【问题标题】:Speeding up virtual function calls in gcc加速 gcc 中的虚函数调用
【发布时间】:2009-04-01 00:11:08
【问题描述】:

使用 gprof 分析我的 C++ 代码,我发现我的大部分时间都花在了一遍又一遍地调用一个虚拟方法上。该方法本身很短,如果它不是虚拟的,可能会被内联。

除了将其全部重写为不是虚拟的之外,还有哪些方法可以加快速度?

【问题讨论】:

  • 能否贴一些代表虚拟功能及其调用方式的代码。
  • 我没用过gprof,但它不是测量函数内部的时间而不是调用时间吗?
  • 虚拟并不意味着非内联。有关详细信息,请参阅我的完整答案。

标签: c++ virtual-functions gprof


【解决方案1】:

您确定时间都与通话有关吗?可能是成本所在的功能本身吗?如果是这种情况,简单地内联可能会使函数从您的分析器中消失,但您不会看到太多的加速。

假设它确实是进行如此多虚拟调用的开销,那么在不使事物变为非虚拟的情况下,您可以做的事情是有限的。

如果调用有诸如时间/标志之类的提前退出,那么我通常会使用两级方法。检查是通过非虚拟调用内联的,仅在必要时才调用特定于类的行为。

例如

class Foo
{
public:

inline void update( void )
{
  if (can_early_out)
    return;

  updateImpl();
}

protected:

virtual void updateImpl( void ) = 0;    
}; 

【讨论】:

    【解决方案2】:

    如果虚拟调用确实是瓶颈,请尝试CRTP

    【讨论】:

      【解决方案3】:

      时间是花在实际的函数调用上,还是花在函数本身上?

      虚函数调用明显比非虚函数调用慢,因为虚函数调用需要额外的解引用。 (如果您想阅读所有毛茸茸的细节,请在 Google 搜索“vtable”。)更新:事实证明 Wikipedia article 在这方面还不错。

      然而,这里的“显着”意味着几条指令如果它消耗了总计算的很大一部分,包括在被调用函数中花费的时间,那么这听起来像是一个考虑非虚拟化和内联的好地方。

      但在 C++ 近 20 年的发展历程中,我认为我从未见过这种情况真的发生过。我很想看看代码。

      【讨论】:

      • +1 在这里谈论整个问题,包括您的经验。我也很难相信通话开销是真正的问题。
      【解决方案4】:

      请注意,“virtual”和“inline”不是对立的——方法可以两者兼而有之。如果编译器可以在编译时确定对象的类型,编译器会很乐意内联一个虚函数:

      struct B {
          virtual int f() { return 42; }
      };
      
      struct D : public B {
          virtual int f() { return 43; }
      };
      
      int main(int argc, char **argv) {
          B b;
          cout << b.f() << endl;   // This call will be inlined
      
          D d;
          cout << d.f() << endl;   // This call will be inlined
      
          B& rb = rand() ? b : d;
          cout << rb.f() << endl;  // Must use virtual dispatch (i.e. NOT inlined)
          return 0;
      }
      

      [更新:确定 rb 的真实动态对象类型在编译时无法获知——感谢 MSalters]

      如果对象的类型可以在编译时确定,但函数不可内联(例如,它很大或在类定义之外定义),它将被非虚拟调用。

      【讨论】:

      • 虽然正确,但这是一种人为的情况,在现实生活中几乎不会发生(哈哈!) - 如果它确实发生了,'f' 甚至不需要在上面的代码中是虚拟的。
      • B& rb = rand() ?乙:d; // 很明显为什么 f 需要是虚拟的。
      • @Andrew:我不同意——我的观点是可以使方法虚拟化,从而实现随之而来的灵活性,同时尽可能不牺牲内联的可用速度。
      • @MSalters:很好的建议,我已经相应地更新了帖子。
      【解决方案5】:

      如果没有可用的 C++ 语法糖,考虑如何用老式的“C”编写代码有时会很有启发意义。有时答案不是使用间接调用。示例见this answer

      【讨论】:

      • +1。这个解决方案是合理的,因为在这种情况下,提问者实际上已经确定了这个函数调用缓慢的原因——但一般来说,在你确定你的代码在哪里花费时间之前,不要使用 switch 进行“优化” .
      【解决方案6】:

      您可以通过更改调用约定从虚拟调用中获得更好的性能。旧的 Borland 编译器有一个 __fastcall 约定,它在 cpu 寄存器中而不是在堆栈中传递参数。

      如果您无法使用虚拟调用并且这几个操作确实很重要,请查看您的编译器文档以了解支持的调用约定。

      【讨论】:

        【解决方案7】:
        猜你喜欢
        • 2011-01-11
        • 1970-01-01
        • 2012-02-06
        • 1970-01-01
        • 1970-01-01
        • 2020-07-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多