C++ 使用指向成员函数的指针与使用开关相比,成本是多少?
我有以下情况:C++ 使用指向成员函数的指针与使用开关相比,成本是多少?,c++,function-pointers,C++,Function Pointers,我有以下情况: class A { public: A(int whichFoo); int foo1(); int foo2(); int foo3(); int callFoo(); // cals one of the foo's depending on the value of whichFoo }; 在我当前的实现中,我将whichFoo的值保存在构造函数的数据成员中,并在callFoo()中使用开关来决定调用哪个foo。或者,我可以在构造
class A
{
public:
A(int whichFoo);
int foo1();
int foo2();
int foo3();
int callFoo(); // cals one of the foo's depending on the value of whichFoo
};
在我当前的实现中,我将whichFoo
的值保存在构造函数的数据成员中,并在callFoo()
中使用开关来决定调用哪个foo。或者,我可以在构造函数中使用开关
保存指向右侧fooN()
的指针,以便在callFoo()
中调用
我的问题是,如果类A的对象只构造一次,而调用callFoo()
的次数非常多,那么哪种方法更有效。因此,在第一种情况下,我们有一个switch语句的多次执行,而在第二种情况下,只有一个switch,并且使用指向它的指针多次调用一个成员函数。我知道使用指针调用成员函数比直接调用要慢。有人知道这个开销是大于还是小于交换机的成本吗
澄清:我意识到,在您尝试并计时之前,您永远不会真正知道哪种方法可以提供更好的性能。然而,在本例中,我已经实现了方法1,我想知道方法2是否至少在原则上更有效。看起来它是可以的,现在我有理由费心去实现它并尝试它
哦,出于美学原因,我也更喜欢方法2。我想我正在寻找一个理由来实施它。:) 如何确定通过指针调用成员函数比直接调用要慢?你能测量出差异吗
一般来说,在进行绩效评估时,你不应该依靠直觉。坐下来看看你的编译器和一个计时函数,实际测量不同的选择。你可能会感到惊讶
更多信息:有一篇很好的文章详细介绍了成员函数指针的实现。听起来你应该把callFoo
变成一个纯虚拟函数,并创建a
的一些子类
除非您真的需要速度,否则您已经完成了大量的分析和检测,并确定对callFoo
的调用确实是瓶颈。您有吗?回答问题:在最细粒度级别上,指向成员函数的指针将执行得更好
解决一个未被问到的问题:“更好”在这里意味着什么?在大多数情况下,我认为差别可以忽略不计。然而,这取决于它在做什么,差异可能是显著的。在担心差异之前进行性能测试显然是正确的第一步。函数指针几乎总是比链式ifs更好。它们生成的代码更简洁,而且几乎总是更快(可能只有在两个函数之间进行选择并且总是正确预测的情况下除外)。如果要继续使用开关,这很好,那么您可能应该将逻辑放在助手方法中,并从构造函数调用If。或者,这是一个典型的例子。您可以创建一个名为IFoo的接口(或抽象类),它有一个带有Foo签名的方法。你可以让构造函数接收一个IFoo实例(实现你想要的foo方法的构造函数)。你可以用这个构造函数设置一个私有的IFoo,每次你想调用foo时,你都会调用你的IFoo版本
注:我从大学起就没有用C++,所以我的语言可能在这里,但大多数OO语言都有一般的观点。 < P>我应该认为指针会更快。
现代CPU预取指令;错误预测的分支刷新缓存,这意味着它在重新填充缓存时会暂停。指针不会这样做
当然,你应该测量两者。你可以这样写:
class Foo {
public:
Foo() {
calls[0] = &Foo::call0;
calls[1] = &Foo::call1;
calls[2] = &Foo::call2;
calls[3] = &Foo::call3;
}
void call(int number, int arg) {
assert(number < 4);
(this->*(calls[number]))(arg);
}
void call0(int arg) {
cout<<"call0("<<arg<<")\n";
}
void call1(int arg) {
cout<<"call1("<<arg<<")\n";
}
void call2(int arg) {
cout<<"call2("<<arg<<")\n";
}
void call3(int arg) {
cout<<"call3("<<arg<<")\n";
}
private:
FooCall calls[4];
};
请注意,您甚至不必在构造函数中修复实际的函数号
我已经将此代码与开关生成的asm进行了比较。开关
版本没有提供任何性能提升。如果您的示例是真实代码,那么我认为您应该重新审视您的类设计。将值传递给构造函数,并使用该值来更改行为实际上等同于创建subcLASS。考虑重构以使其更加明确。这样做的效果是,您的代码将最终使用函数指针(所有虚拟方法实际上都是跳转表中的函数指针)。
然而,如果你的代码只是一个简单的例子,询问跳转表是否通常比switch语句快,那么我的直觉会说跳转表更快,但是你依赖于编译器的优化步骤。但是如果性能真的是一个问题,千万不要依赖直觉-启动一个测试程序和测试或者看看生成的汇编程序
有一点是肯定的,switch语句永远不会比跳转表慢。原因是编译器的优化程序所能做的最好的事情就是太过频繁地进行一系列条件测试(即switch)因此,如果您确实想确定,请将编译器从决策过程中取出,并使用跳转表。仅在需要时进行优化
第一:在大多数情况下,您很可能不在乎,差异将非常小。首先确保优化此调用确实有意义。只有当您的测量结果显示在调用开销上花费了非常长的时间时,才继续优化它(无耻的插件-Cf.)如果优化不重要,则首选可读性更强的代码
间接呼叫成本取决于目标平台
一旦您确定应用低级优化是值得的,那么现在就是了解目标平台的时候了。您可以避免的成本是分支预测失误惩罚。在现代x86/x6上
(this->*(calls[number]))(arg);
004142E7 mov esi,esp
004142E9 mov eax,dword ptr [arg]
004142EC push eax
004142ED mov edx,dword ptr [number]
004142F0 mov eax,dword ptr [this]
004142F3 mov ecx,dword ptr [this]
004142F6 mov edx,dword ptr [eax+edx*4]
004142F9 call edx