C++ 虚函数与成员函数指针(性能比较)
c++ virtual function vs member function pointer (performance comparison)
虚拟函数调用可能会很慢,因为虚拟调用需要对向量表进行额外的索引遵从,这可能导致数据缓存未命中以及指令缓存未命中...不利于性能关键型应用程序。
因此,我一直在想一种方法来克服虚拟函数的性能问题,同时仍然具有虚拟函数提供的一些相同功能。
我相信以前已经这样做了,但我设计了一个简单的测试,允许基类存储可由任何派生类设置的成员函数指针。当我在任何派生类上调用 Foo(( 时,它将调用相应的成员函数,而无需遍历逆向表......
我只是想知道这种方法是否是虚拟呼叫范式的可行替代品,如果是这样,为什么它不是更普遍?
提前感谢您的时间! :)
class BaseClass
{
protected:
// member function pointer
typedef void(BaseClass::*FooMemFuncPtr)();
FooMemFuncPtr m_memfn_ptr_Foo;
void FooBaseClass()
{
printf("FooBaseClass() n");
}
public:
BaseClass()
{
m_memfn_ptr_Foo = &BaseClass::FooBaseClass;
}
void Foo()
{
((*this).*m_memfn_ptr_Foo)();
}
};
class DerivedClass : public BaseClass
{
protected:
void FooDeriveddClass()
{
printf("FooDeriveddClass() n");
}
public:
DerivedClass() : BaseClass()
{
m_memfn_ptr_Foo = (FooMemFuncPtr)&DerivedClass::FooDeriveddClass;
}
};
int main(int argc, _TCHAR* argv[])
{
DerivedClass derived_inst;
derived_inst.Foo(); // "FooDeriveddClass()"
BaseClass base_inst;
base_inst.Foo(); // "FooBaseClass()"
BaseClass * derived_heap_inst = new DerivedClass;
derived_heap_inst->Foo();
return 0;
}
我做了一个测试,使用虚函数调用的版本在我的系统上通过优化更快。
$ time ./main 1
Using member pointer
real 0m3.343s
user 0m3.340s
sys 0m0.002s
$ time ./main 2
Using virtual function call
real 0m2.227s
user 0m2.219s
sys 0m0.006s
这是代码:
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <stdio.h>
struct BaseClass
{
typedef void(BaseClass::*FooMemFuncPtr)();
FooMemFuncPtr m_memfn_ptr_Foo;
void FooBaseClass() { }
BaseClass()
{
m_memfn_ptr_Foo = &BaseClass::FooBaseClass;
}
void Foo()
{
((*this).*m_memfn_ptr_Foo)();
}
};
struct DerivedClass : public BaseClass
{
void FooDerivedClass() { }
DerivedClass() : BaseClass()
{
m_memfn_ptr_Foo = (FooMemFuncPtr)&DerivedClass::FooDerivedClass;
}
};
struct VBaseClass {
virtual void Foo() = 0;
};
struct VDerivedClass : VBaseClass {
virtual void Foo() { }
};
static const size_t count = 1000000000;
static void f1(BaseClass* bp)
{
for (size_t i=0; i!=count; ++i) {
bp->Foo();
}
}
static void f2(VBaseClass* bp)
{
for (size_t i=0; i!=count; ++i) {
bp->Foo();
}
}
int main(int argc, char** argv)
{
int test = atoi(argv[1]);
switch (test) {
case 1:
{
std::cerr << "Using member pointern";
DerivedClass d;
f1(&d);
break;
}
case 2:
{
std::cerr << "Using virtual function calln";
VDerivedClass d;
f2(&d);
break;
}
}
return 0;
}
编译使用:
g++ -O2 main.cpp -o main
使用 G++ 4.7.2。
由于虚拟调用必须遍历向量表,因此虚函数调用可能会很慢,
这不太正确。vtable 应在对象构造上计算,每个虚函数指针设置为层次结构中最专业的版本。调用虚函数的过程不是迭代指针,而是调用类似 *(vtbl_address + 8)(args);
的东西,它是在常量时间内计算的。
这可能导致数据缓存未命中以及指令缓存未命中...不利于性能关键型应用程序。
您的解决方案也不适合性能关键型应用程序(通常(,因为它是通用的。
通常,性能关键型应用程序会根据具体情况进行优化(测量,选择模块中性能问题最差的代码并进行优化(。
使用这种按大小写的方法,您可能永远不会遇到代码运行缓慢的情况,因为编译器必须遍历 vtbl。如果是这种情况,那么缓慢可能来自通过指针而不是直接调用函数(即问题将通过内联来解决,而不是通过在基类中添加额外的指针(。
无论如何,所有这些都是学术性的,直到你有一个具体的案例需要优化(并且你已经测量到你最严重的罪魁祸首是虚函数调用(。
编辑:
我只是想知道这种方法是否是虚拟呼叫范式的可行替代品,如果是这样,为什么它不是更普遍?
因为它看起来像一个通用解决方案(普遍应用它会降低性能而不是提高它(,所以解决了一个不存在的问题(您的应用程序通常不会因虚函数调用而变慢(。
虚函数不会"遍历"表,只需从某个位置获取指针并调用该地址即可。这就好像您手动实现了指向函数的指针,并将其用于调用而不是直接调用。
因此,您的工作只适合混淆,并破坏编译器可以发出非虚拟直接调用的情况。
使用指向成员的指针函数可能比 PTF 更糟糕,它可能会使用相同的 VMT 结构进行类似的偏移访问,只是一个变量而不是固定的。
主要是因为它不起作用。大多数现代 CPU 在分支预测和推测执行方面比您想象的要好。但是,我还没有看到一个CPU在非静态分支之外执行推测执行。
此外,在现代 CPU 中,您更有可能出现缓存未命中,因为您在调用之前有一个上下文切换并且另一个程序接管了缓存,而不是因为对向表,即使这种情况也是一个非常遥远的可能性。
实际上,一些编译器可能会使用 thunks,它们本身可以转换为普通的函数指针,所以基本上编译器会为您执行您尝试手动执行的操作(并且可能会混淆人们(。
此外,有一个指向虚函数表的指针,虚函数的空间复杂度为 O(1((只是指针(。另一方面,如果在类中存储函数指针,则复杂度为 O(N((您的类现在包含的指针数量与"虚"函数的数量一样多(。如果有许多函数,您将为此付出代价 - 在预取对象时,您将加载缓存行中的所有指针,而不仅仅是单个指针和您可能需要的前几个成员。这听起来像是一种浪费。
另一方面,虚拟函数表位于一种类型的所有对象的一个位置,并且可能永远不会从缓存中推出,而您的代码在循环中调用一些短的虚函数(这可能是虚函数成本成为瓶颈时的问题(。
至于分支预测,在某些情况下,对象类型的简单决策树和每个特定类型的内联函数提供了良好的性能(然后您存储类型信息而不是指针(。这并不适用于所有类型的问题,并且主要是过早的优化。
根据经验,不要担心语言结构,因为它们看起来很陌生。只有在测量并确定瓶颈真正所在之后,才担心和优化。
- 如何比较两个函数的速度和性能
- 调用不在基类中的派生类函数而不进行动态强制转换,以最大程度地提高性能
- 函数局部静态变量:从性能角度来看的优点/缺点
- C++ 将函数指针与最佳性能相结合
- 为什么使用默认构造函数"{}"而不是"= default"存在性能变化?
- 通过默认复制构造函数比较 C++ 字符串是否会影响性能,原因为何?
- 隐式转换函数的返回对象时是否会影响性能?
- 虚拟函数调用的性能作为 for 循环中的上限
- 如何提高对 std::函数侦听器的分发性能?
- 性能函数调用与乘以 1
- C++构造函数性能
- 课堂初始化(分配样式)与构造函数性能
- 类方法VS类静态函数VS简单函数-性能方面
- 对同一对象进行x调用的c++虚拟函数性能
- 由派生类调用时的虚函数性能
- c++ 11委托的函数是否比c++ 03调用init函数的函数性能差?
- 捕获Lambda函数性能的影响
- Visual c++中的函数性能计数器
- 从另一个操作符重载函数调用操作符重载函数:性能
- 内联函数性能