当"虚拟"是一个相当大的开销时,有没有经验法则?

luk*_*k32 7 c++ performance

我的问题基本上已在标题中完整陈述,但让我详细说明.

问题: 也许值得重新说明,virtual方法必须多么复杂/简单,使机制成为一个相当大的开销?这有什么经验法则吗?例如,如果需要10分钟,使用I/O,复杂if语句,内存操作等,这不是问题.或者,如果您virtual get_r() { return sqrt( x*x + y*y); };在循环中编写并调用它,您将遇到麻烦.

我希望这个问题不是太笼统,因为我寻求一些一般但具体的技术答案.无论是难以分辨还是不可能,或者虚拟调用需要花费如此多的时间/周期资源,而数学需要这样,I/O就是这样.

也许一些技术人员知道要比较的一般数字或进行一些分析,并且可以分享一般性结论.令人难以置信的是,我不知道如何进行那些花哨的asm分析= /.

我还想提出一些理由,以及我的用例.

我认为我看到的问题不仅仅是人们在干旱期间为了表现而不在森林中使用像开放式火灾这样的虚拟现象,而是为了表现,并且很多人都问他们"你是否绝对确定虚拟开销在你的案例中确实是一个问题?".

在我最近的工作中,我遇到了一个可以放在河两岸的问题,我相信.

另外请记住我不问如何改进界面的实现.我相信我知道该怎么做.我问是否有可能告诉他们什么时候做,或者选择哪种权利.

用例:

我运行了一些模拟.我有一个基本上提供运行环境的类.有一个基类,以及一个定义一些不同工作流的派生类.Base将东西收集为通用逻辑并分配I/O源和接收器.衍生品通过实施或多或少地定义特定的工作流程RunEnv::run().我认为这是一个有效的设计.现在让我们假设作为工作流主体的对象可以放在2D或3D平面中.工作流程在两种情况下都是通用/可互换的,因此我们正在处理的对象可以具有通用接口,尽管对于非常简单的方法Object::get_r().最重要的是,我们可以为环境定义一些stat logger.

最初我想提供一些代码片段,但最终有5个类和2-4个方法,每个都是墙code.我可以根据要求发布它,但它会将问题延长到当前大小的两倍.

关键点是:RunEnv::run()是主循环.通常很长(5分钟-5小时).它提供基本的时间检测,呼叫RunEnv::process_iteration()RunEnv::log_stats().一切都是虚拟的.理由是.我可以推导出RunEnv,run()例如针对不同的停止条件重新设计.我可以重新设计process_iteration(),例如使用多线程,如果我必须处理一个对象池,以各种方式处理它们.此外,不同的工作流程还需要记录不同的统计信息.RunEnv::log_stats()只是一个将已计算的有趣统计数据输出到a中的调用std::ostream.我使用虚拟机并没有真正的影响.

现在让我们假设迭代通过计算对象到原点的距离来工作.所以我们有接口double Obj::get_r();.Obj是2D和3D案例的实现.在两种情况下,getter都是一个简单的数学运算,有2-3次乘法和加法.

我还尝试了不同的内存处理.例如,有时坐标数据存储在私有变量中,有时存储在共享池中,因此即使get_x()可以通过实现get_x(){return x;};或虚拟来实现get_x(){ return pool[my_num*dim+x_offset]; };.想象一下计算一些东西get_r(){ sqrt(get_x()*get_x() + get_y()*get_y()) ;};.我怀疑这里的虚拟会破坏性能.

Ser*_* K. 8

在x86上使用C++进行虚拟方法调用会产生类似于(单继承)的代码:

    mov ecx,[esp+4]
    mov eax,[ecx]       // pointer to vtable
    jmp [eax]           
Run Code Online (Sandbox Code Playgroud)

如果没有虚拟,则mov与非虚拟成员函数相比,您将省略一条指令.因此,在单继承的情况下,性能损失可以忽略不计.

如果您有多个继承,或者更糟糕的是虚拟继承,虚拟调用可能会复杂得多.但这是类层次结构和体系结构的更多问题.

经验法则:

如果方法的主体比单个mov指令慢很多倍(> 100x)- 只需使用virtual而不要打扰.否则 - 描述您的瓶颈并进行优化.

更新:

有关多个/虚拟继承的情况,请查看此页面:http://www.lrdev.com/lr/c/virtual.html

  • 在超标量体系结构上,问题不是`mov`,而是`jmp`到非固定地址,如果分支预测器还没有任何关于它的信息,或者它做出了错误的猜测,则引入了相当大的减速. (4认同)
  • 无论如何,在性能很重要的情况下(通常是内部循环),如果对象的类型不是每时每刻都会改变,那么分支预测器应该快速拾取,从而消除了分支误预测惩罚; 此外,编译器通常足够智能,以避免虚拟调度在许多情况下,它可以毫无疑问地确定编译时对象的类型(=只有在实际需要"虚拟"调度时才支付"虚拟"的代价). (2认同)

Cal*_*leb 8

这有什么经验法则吗?

对于像这样的问题,最好的,最一般的经验法则是:

在优化之前测量您的代码

尝试使您的代码在不进行测量的情况下运行良好,这是通往不必要的复杂代码的可靠途径,这些代码在所有错误的位置进行了优化.

所以,在你有一些确凿的证据证明virtual是问题之前,不要担心虚函数的开销.如果您确实有这样的证据,那么您可以virtual在这种情况下删除.但是,更有可能的是,您会发现找到加快计算速度的方法,或者避免计算您不需要的计算方法,将会产生更大的性能提升.但同样,不要只是猜测 - 首先测量.