C++ 派生*调用非虚拟基';调用虚函数的s函数-静态解析的力
在间接调用派生类中的虚函数时,是否可能强制编译器对其进行静态解释,以避免vtable开销?为什么? 例子 我创建了一个测试来研究C++ 派生*调用非虚拟基';调用虚函数的s函数-静态解析的力,c++,optimization,c++14,final,vtable,C++,Optimization,C++14,Final,Vtable,在间接调用派生类中的虚函数时,是否可能强制编译器对其进行静态解释,以避免vtable开销?为什么? 例子 我创建了一个测试来研究final-关键字对vtable成本的影响 类B派生自类A A::f1()是一个非虚拟函数 A::f2()是一个虚拟函数B覆盖它 A::f3()是一个虚拟函数B覆盖它并将其标记为最终 A::f4()是一个非虚拟函数。它调用A::f3() 我概述并注意到功能的成本(相对而言):- B*->f1()=160 B*->f2()=270:“虚拟”成本很高 B*->f3()
final
-关键字对vtable成本的影响
- 类
派生自类B
A
是一个非虚拟函数A::f1()
是一个虚拟函数<代码>B覆盖它A::f2()
是一个虚拟函数<代码>B覆盖它并将其标记为最终A::f3()
是一个非虚拟函数。它调用A::f3()A::f4()
=160B*->f1()
=270:“虚拟”成本很高B*->f2()
=160:“最终”收益率性能增益李>B*->f3()
=270:为什么不是160f4()将调用B*->f4()
,因此应该没有v-table成本 问题:B*->f3()
- 为什么它不知道
- 我相信如果它是静态解析的,编译器将不可能在从
派生的每个类之间共享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; }
A类{ public:int f1(){返回随机数*3;}; public:virtualint f2(){return randomNumber*3;}; public:virtualint f3(){return randomNumber*3;}; public:int f4(){return f3();}; public:int randomNumber=((双)rand()/(rand_MAX))*10; }; B类:公共A{ public:virtualint f2(){return randomNumber*4;}; public:virtualint f3()final{return randomNumber*4;}; }; int main(){ std::向量bs; 常数int numTest=10000;
对于(int n=0;n而言,问题在于在您的示例中没有
。因此,唯一的B::f4()
是f4
。并且必须处理来自A的所有派生类 正如您所注意到的,您可以编写自己的A::f4()
,然后重载(而不是重写)。编译器会在知道您正在访问B时调用B::f4()
。在B::f4()
中,编译器应该足够聪明,可以直接使用B::f4()
如果通过a引用或指针访问B,编译器将继续使用B::f3()
当我在编译器资源管理器上尝试这一点时,它只有2017编译器a::f4()
被内联在B::f3
中,并按预期将两者都内联到调用函数中 当我没有定义B::f4
时,B::f4
是内联的,仍然执行虚拟函数调用 在内联f4之后,您的编译器似乎无法很好地解释虚拟函数调用。我只能详细推测Microsoft编译器是如何工作的,但gcc和LLVM编译为与语言无关的中间形式(分别为格式和格式)然后这就变成了一个别名问题,编译器必须静态地证明虚拟表中的条目始终是A::f4
。通常不能确定,不幸的是,关于最终方法的信息似乎传播得不够远。GCC至少在有利可图的情况下会这样做 当没有内联发生时,我认为编译器将很难对其进行优化,即使它一次看到了所有的定义,但这并不能保证。 为类型B的对象提供一个额外的B::f3
的“专门化”在理论上是可行的,但我不确定它是否提供了足够的平均案例性能,是否值得编译器开发人员考虑 实现f4的一种方法是,编译器生成您想要的代码变体,而无需您重复自己的代码变体,该方法将作为一个模板函数,位于:A::f4
template <typename DerivedFromA> inline int f4(DerivedFromA &x) { return x.f3(); }
您应该尝试将模板 内联整数f4(从A&x派生) { 返回x.f3(); }
声明为final。也许编译器会尝试对某些调用进行Device。但一般来说,您不应该过分依赖编译器。@VTT没有帮助,B
您的意思是演示中的f4()变得更昂贵。使用RexTealter,@ MARS,根据VisualC++ ++代码SooCy.Fiel.CPO-O2-AEXE/ EHsc / MD /I C:\BooStY116000/Loo/LBPATE:C:\BooSTy1160600\ListB/<代码>结果与非最终版本相比有所改进。谢谢。1。eem能够做到这一点
在gcc中应该是快速的吗?一个证据(使用的时间,gcc)显示它不能内联。这与你的说法相矛盾。但我不确定rextester是2017年的。你在你的计算机或在线网站上测试过它吗?2.f4()
:你能提供一个参考吗?这对我来说是新知识。1.我在gcc.godbolt.com上用一个简化的exa尝试过它示例。但是,我犯了一个错误,使用了B的局部变量。然后编译器知道的比使用B指针时知道的要多。请参见此示例:。在那里,只执行了推测性的设备化。我将纠正这一点。2.我添加了指向各自格式说明的链接。我相信,通过谷歌搜索,您会发现更好的介绍奥斯。gcc和LLVM编译成一种语言不可知的中间形式