我有一个看起来像Boost.Array的简单类。有两个模板参数T和N.Boost.Array的一个缺点是,每个使用这种数组的方法都必须是一个参数为N的模板(T是OK)。结果是整个程序往往是一个模板。一个想法是创建一个仅依赖于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表现出相同的行为。
答案 0 :(得分:2)
此行为是预期的:您正在调用虚方法。无论是直接调用它还是通过基类指针调用都是首先不相关:在这两种情况下,调用都必须通过虚函数表。
对于一个简单的调用,例如Get
(它只是解引用数组单元格,可能没有边界检查),这确实可以产生因子4的差异。
现在,一个好的编译器可以看到这里不需要添加的间接,因为在编译时已知对象的动态类型(因此方法调用目标)。我有点惊讶GCC显然没有对此进行优化(你是否使用-O3
编译?)。然后,这只是一次优化。
答案 1 :(得分:2)
我不同意你的结论“整个程序往往是一个模板”:它让我觉得你正试图解决一个非问题。
但是,不清楚'扩展Boost.Array带接口'是什么意思:你是通过引入你的界面来修改boost::array
的来源吗?如果是这样,您创建的每个array
实例都必须拖动vtable指针,无论您是否使用虚拟方法。虚方法的存在也可能使编译器对在纯粹的头定义类中使用非虚方法的积极优化保持警惕。
已编辑:...当然,您 正在使用虚拟方法。它需要非常先进的代码分析技术,以确保编译器可以优化虚拟调用。
答案 2 :(得分:1)
有两个原因:
答案 3 :(得分:0)
如果你有一个永远不会扩展的虚方法,那么编译器很可能正在优化方法的虚拟部分。在正常的非虚方法调用期间,程序流将直接从调用者转到方法。但是,当该方法标记为虚拟时,cpu必须首先跳转到虚拟表,然后找到您要查找的方法,然后跳转到该方法。
现在这通常不太明显。如果您正在调用的方法需要100毫秒才能执行,即使您的虚拟表查找需要1毫秒,也无关紧要。但是,如果在数组的情况下,您的方法需要0.5ms才能执行1ms的性能下降将非常明显。
除了不扩展Boost.Array或重命名方法以便它们不会覆盖之外,你无能为力。