【发布时间】:2017-08-08 10:01:45
【问题描述】:
当间接调用派生类中的虚函数时,是否可以强制编译器对其进行静态解释,以避免 vtable-cost?为什么?
示例
我创建了一个测试来研究final-keyword 对 vtable 成本的影响。
- 类
B派生自类A。 -
A::f1()是一个非虚函数。 -
A::f2()是一个虚函数。B覆盖它。 -
A::f3()是一个虚函数。B覆盖它并将其标记为 final。 -
A::f4()是一个非虚函数。它调用A::f3()。
Full Demo
我分析并注意到功能的成本是(相对):-
-
B*->f1()= 160 -
B*->f2()= 270 :“虚拟”成本很高。 -
B*->f3()= 160:“最终”收益性能提升! -
B*->f4()= 270:为什么不是 160?
编译器似乎在查看B::f4(),并尝试调用A::f3(),查看vtable,然后调用B::f3()。
我相信编译器应该静态地知道B*->f4()会调用B*->f3(),所以应该没有v表开销。
问题
- 为什么它不知道?
- 我相信如果它被静态解析,编译器将不可能在派生自
A的每个类之间共享f4()的(二进制/汇编)代码。因此,这是为了防止“代码膨胀”,对吗? - 如果是这样,我该如何强制“代码膨胀”?我仍然希望
f4出现在A中,而不是出现在B中。
这是测试。
class A{
public: int f1(){return randomNumber*3;};
public: virtual int f2(){return randomNumber*3;};
public: virtual int f3(){return randomNumber*3;};
public: int f4(){return f3();};
public: int randomNumber=((double) rand() / (RAND_MAX))*10;
};
class B : public A {
public: virtual int f2() {return randomNumber*4;};
public: virtual int f3()final {return randomNumber*4;};
};
int main(){
std::vector<B*> bs;
const int numTest=10000;
for(int n=0;n<numTest;n++){
bs.push_back(new B());
};
int accu=0;
for(int n=0;n<numTest;n++){
accu+=bs[n]->f1(); //warm
};
auto t1= std::chrono::system_clock::now();
for(int n=0;n<numTest;n++){
accu+=bs[n]->f1(); //test 1 : base case, non virtual
};
auto t2= std::chrono::system_clock::now();
for(int n=0;n<numTest;n++){
accu+=bs[n]->f2(); //test 2: virtual
};
auto t3= std::chrono::system_clock::now();
for(int n=0;n<numTest;n++){
accu+=bs[n]->f3(); //test 3: virtual & final
};
auto t4= std::chrono::system_clock::now();
for(int n=0;n<numTest;n++){
accu+=bs[n]->f4(); //test 4: virtual & final & encapsulator
};
auto t5= std::chrono::system_clock::now();
auto t21=t2-t1;
auto t32=t3-t2;
auto t43=t4-t3;
auto t54=t5-t4;
std::cout<<"test1 base ="<<t21.count()<<std::endl;
std::cout<<"test2 virtual ="<<t32.count()<<std::endl;
std::cout<<"test3 virtual & final ="<<t43.count()<<std::endl;
std::cout<<"test4 virtual & final & indirect="<<t54.count()<<std::endl;
std::cout<<"forbid optimize"<<accu;
}
对不起,如果我使用了错误的行话,我对 C++ 很陌生。
这个问题来自好奇心。
在实践中,可以通过将f4()移动到B来解决,但我想知道它背后的原理。
【问题讨论】:
-
您应该尝试将
B声明为最终结果。也许编译器会尝试去虚拟化一些调用。但总的来说你不应该过度依赖编译器。 -
@VTT 无济于事,
f4()变得更加昂贵。 rextester.com/CIG54180 -
你使用什么编译器和编译器标志?
-
@mars 根据 rextester,我正在使用 Visual C++
source_file.cpp -O2 -o a.exe /EHsc /MD /I C:\boost_1_60_0 /link /LIBPATH:C:\boost_1_60_0\stage\lib -
与非最终版本相比,test2 的结果确实有所改善。
标签: c++ optimization c++14 final vtable