5

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

有什么方法可以加快速度,而不是将其全部重写为非虚拟的?

4

7 回答 7

9

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

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

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

例如

class Foo
{
public:

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

  updateImpl();
}

protected:

virtual void updateImpl( void ) = 0;    
}; 
于 2009-04-01T00:16:59.620 回答
6

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

虚函数调用明显比非虚函数调用慢,因为虚函数调用需要额外的解引用。(如果您想阅读所有详细信息,请使用 Google 搜索“vtable”。) )更新:事实证明,维基百科的文章在这方面还不错。

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

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

于 2009-04-01T00:18:53.110 回答
6

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

于 2009-04-01T00:33:48.550 回答
5

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

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]

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

于 2009-04-01T02:58:51.087 回答
1

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

于 2009-04-01T21:00:30.350 回答
1

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

如果您坚持使用虚拟调用并且这几个操作确实很重要,那么请检查您的编译器文档以获取支持的调用约定。

于 2009-04-02T00:20:20.603 回答
0

这是使用 RTTI 的一种可能方法。

于 2009-06-30T04:46:26.290 回答