我一直听到这个说法。 Switch ..Case是代码维护的邪恶,但它提供了更好的性能(因为编译器可以内联东西等)。虚函数非常适合代码维护,但它们会导致两个指针间接的性能损失。
假设我有一个带有2个子类(X和Y)和一个虚函数的基类,因此会有两个虚拟表。该对象有一个指针,根据该指针选择一个虚拟表。所以对于编译器来说,它更像是
switch( object's function ptr )
{
case 0x....:
X->call();
break;
case 0x....:
Y->call();
};
那么为什么虚拟函数会花费更多,如果它可以通过这种方式实现,因为编译器可以在这里执行相同的内联和其他内容。或者解释一下,为什么决定不以这种方式实现虚函数执行?
谢谢, 戈库尔。
答案 0 :(得分:2)
由于单独的编译模型,编译器无法做到这一点。
在编译虚函数调用时,编译器无法确定有多少不同的子类。
考虑以下代码:
// base.h
class base
{
public:
virtual void doit();
};
和此:
// usebase.cpp
#include "base.h"
void foo(base &b)
{
b.doit();
}
当编译器在foo
中生成虚拟调用时,它不知道运行时将存在哪个base的子类。
答案 1 :(得分:1)
您的问题取决于对交换机和虚拟功能的工作方式的误解。我不会用代码生成的长篇论文来填充这个框,而是给出一些要点:
答案 2 :(得分:1)
以下是具体测试的一些结果。这些特定结果来自VC ++ 9.0 / x64:
Test Description: Time to test a global using a 10-way if/else if statement
CPU Time: 7.70 nanoseconds plus or minus 0.385
Test Description: Time to test a global using a 10-way switch statement
CPU Time: 2.00 nanoseconds plus or minus 0.0999
Test Description: Time to test a global using a 10-way sparse switch statement
CPU Time: 3.41 nanoseconds plus or minus 0.171
Test Description: Time to test a global using a 10-way virtual function class
CPU Time: 2.20 nanoseconds plus or minus 0.110
对于稀疏情况,switch语句要慢得多。对于密集的情况,switch语句可能会更快,但是开关和虚函数调度重叠一点,所以当开关可能更快时,边距太小我们甚至无法确定它更快,更不用说足够快得多关心了。如果switch语句中的情况完全是稀疏的,那么虚函数调用的速度就会越来越快。
答案 3 :(得分:0)
虚拟调度中没有分支。类中的vptr指向一个vtable,其中第二个指针用于常量偏移处的特定函数。
答案 4 :(得分:0)
关于调用虚函数时分支的说法是错误的。生成的代码中没有这样的东西。看看汇编代码会给你一个更好的主意。
在一个坚果shell中,C ++虚函数的一个通用简化实现是:每个类都有一个虚拟表(vbtl),并且该类的每个实例都有一个虚拟表指针(vptr)。虚拟表基本上是一个函数指针列表。
当您调用虚拟函数时,请说它是:
class Base {};
class Derived {};
Base* pB = new Derived();
pB->someVirtualFunction();
'someVirtualFunction()'将在vtbl中具有相应的索引。和电话
pB->someVirtualFunction();
将转换为类似:
pB->vptr[k](); //k is the index of the 'someVirtualFunction'.
通过这种方式,函数实际上是间接调用的,并且具有多态性。
我建议你阅读Stanley Lippman撰写的'The C++ Object Model'。
此外,虚函数调用比switch-case慢的语句不会产生。这取决于。正如您在上面所看到的,与常规函数调用相比,虚函数调用只需要一次额外的引用时间。使用switch-case分支,你会有额外的比较逻辑(这会导致CPU丢失缓存的可能性),这也消耗CPU周期。我会说在大多数情况下,如果不是全部,虚拟函数调用应该比switch-case更快。
答案 5 :(得分:0)
实际上,如果你有很多虚函数,那么类似switch的分支将比两个指针间接更慢。当前实现的性能并不取决于您拥有多少虚拟功能。
答案 6 :(得分:0)
明确地说switch/case
比虚拟调用更多或更少的性能是一种过度概括。事实是,这将取决于许多事情,并将根据以下内容而有所不同:
如果您在编写代码时正在优化代码,那么您很可能做出错误的选择。首先以人类可读和/或用户友好的方式编写代码,然后通过分析工具运行整个可执行文件。如果代码的这个区域显示为热点,那么请尝试两种方式,看看哪种方式可以量化为您的特定情况。
答案 7 :(得分:0)
这种优化只能通过重新定位链接器实现,该链接器应作为C ++运行时的一部分运行。
C ++运行时更复杂,即使新的DLL加载(使用COM)也会向vtable添加新的函数指针。 (想想纯粹的虚拟fns?)
然后编译器或链接器都无法进行此优化。 switch / case明显比间接调用快,因为CPU中的预取是确定性的并且可以进行流水线操作。但是由于对象的vtable的运行时扩展,它在C ++中无法解决。