4

我有一个看起来像 Boost.Array 的简单类。有两个模板参数 T 和 N。Boost.Array 的一个缺点是,每个使用这种数组的方法都必须是一个带有参数 N 的模板(T 是可以的)。结果是整个程序往往是一个模板。一个想法是创建一个仅依赖于 T(类似于 ArrayInterface)的接口(仅具有纯虚函数的抽象类)。现在每个其他类只访问接口,因此只需要模板参数 T(与 N 相比,它或多或少总是已知的)。如果使用接口,这里的缺点是虚拟调用的开销(更多错失内联调用的机会)。直到这里只有事实。

template<typename T>
class ArrayInterface {
public:
    virtual ~ArrayInterface() {};
    virtual T Get(int i) = 0;
};

template<typename T, int N>
class Array : ArrayInterface<T> {
public:
    T Get(int i) { ... }
};

template<typename T, int N>
class ArrayWithoutInterface {
public:
    T Get() { ... }
};

但我真正的问题在于其他地方。当我使用接口扩展 Boost.Array 时,Boost.Array 的直接实例化会变慢(在一种情况下,因子 4,这很重要)。如果我删除接口,Boost.Array 和以前一样快。我了解,如果通过 ArrayInterface 调用方法存在开销,那没关系。但是我不明白如果只有一个只有纯虚拟方法的附加接口并且直接调用该类,为什么对方法的调用会变慢。

Array<int, 1000> a;
a.Get(0); // Slow

ArrayWithoutInterface<int, 1000> awi;
awi.Get(0); // Fast

GCC 4.4.3 和 Clang 1.1 表现出相同的行为。

4

4 回答 4

2

这种行为是预期的:您正在调用一个虚拟方法。无论您是直接调用它还是通过基类指针调用它一开始都无关紧要:在这两种情况下,调用都必须通过虚函数表。

对于一个简单的调用,例如Get(简单地取消引用数组单元,大概没有边界检查),这确实可以产生 4 倍的差异。

现在,一个好的编译器可以看到这里不需要添加间接,因为对象的动态类型(以及方法调用目标)在编译时是已知的。我有点惊讶 GCC 显然没有优化这个(你编译了-O3吗?)。再说一次,这只是一个优化。

于 2010-11-05T13:35:49.603 回答
2

我不同意您的结论,即“整个程序往往是一个模板”:在我看来,您正在尝试解决一个非问题。

但是,不清楚您所说的“使用接口扩展 Boost.Array”是什么意思:您是否boost::array通过引入接口来修改源代码?array如果是这样,无论您是否使用虚拟方法,您创建的每个实例都必须拖动一个 vtable 指针。虚拟方法的存在也可能使编译器对可能在纯标头定义的类中对非虚拟方法使用激进的优化持谨慎态度。

编辑:...当然,您使用的是虚拟方法。编译器需要非常先进的代码分析技术才能确定可以优化虚拟调用。

于 2010-11-05T13:37:15.997 回答
1

两个原因:

  • 后期绑定很慢
  • 虚方法不能内联
于 2010-11-05T13:33:23.237 回答
0

如果您有一个永远不会扩展的虚拟方法,那么编译器很可能正在优化方法的虚拟部分。在正常的非虚拟方法调用期间,程序流将直接从调用者转到方法。但是,当方法被标记为virtual时,cpu必须先跳转到一个虚拟表,然后找到你要找的方法,然后再跳转到那个方法。

现在这通常不会太明显。如果您调用的方法需要 100 毫秒来执行,即使您的虚拟表查找需要 1 毫秒,也没有关系。但是,如果在数组的情况下,您的方法需要 0.5 毫秒来执行,那么 1 毫秒的性能下降将非常明显。

除了不扩展 Boost.Array 或重命名您的方法以使其不会被覆盖之外,您对此无能为力。

于 2010-11-05T13:33:52.373 回答