【问题标题】:Why is execution-time method resolution faster than compile-time resolution?为什么执行时方法解析比编译时解析更快?
【发布时间】:2011-02-24 21:23:01
【问题描述】:

在学校,我们讨论了 C++ 中的virtual 函数,以及它们是如何解析(或找到,或匹配,我不知道'不知道术语是什么——我们不是用英语学习)在执行时而不是编译时。老师还告诉我们,编译时解析比执行时快得多(这样做是有道理的)。然而,一个快速的实验会提出不同的建议。我已经构建了这个小程序:

#include <iostream>
#include <limits.h>

using namespace std;

class A {
    public:
    void f() {
        // do nothing
    }
};

class B: public A {
    public:
    void f() {
        // do nothing
    }
};

int main() {
    unsigned int i;
    A *a = new B;
    for (i=0; i < UINT_MAX; i++) a->f();
    return 0;
}

我编译了上面的程序并将其命名为normal。然后,我将A 修改为如下所示:

class A {
    public:
    virtual void f() {
        // do nothing
    }
};

编译并命名为virtual。这是我的结果:

[felix@the-machine C]$ time ./normal 

real    0m25.834s
user    0m25.742s
sys 0m0.000s
[felix@the-machine C]$ time ./virtual 

real    0m24.630s
user    0m24.472s
sys 0m0.003s
[felix@the-machine C]$ time ./normal 

real    0m25.860s
user    0m25.735s
sys 0m0.007s
[felix@the-machine C]$ time ./virtual 

real    0m24.514s
user    0m24.475s
sys 0m0.000s
[felix@the-machine C]$ time ./normal 

real    0m26.022s
user    0m25.795s
sys 0m0.013s
[felix@the-machine C]$ time ./virtual 

real    0m24.503s
user    0m24.468s
sys 0m0.000s

虚拟版本似乎有大约 1 秒的稳定差异。这是为什么呢?


相关与否:双核奔腾 @ 2.80Ghz,两次测试之间没有运行额外的应用程序。 Archlinux 与 gcc 4.5.0。正常编译,如:

$ g++ test.cpp -o normal

另外,-Wall 也不会发出任何警告。


编辑:我已将我的程序分成A.cppB.cppmain.cpp。另外,我创建了f()A::f()B::f())函数实际上一些事情(x = 0 - x 其中xpublicintA 的成员, 在A::A() 中初始化为 1)。编译成六个版本,这是我的最终结果:

[felix@the-machine poo]$ time ./normal-unoptimized 

real    0m31.172s
user    0m30.621s
sys 0m0.033s
[felix@the-machine poo]$ time ./normal-O2

real    0m2.417s
user    0m2.363s
sys 0m0.007s
[felix@the-machine poo]$ time ./normal-O3

real    0m2.495s
user    0m2.447s
sys 0m0.000s
[felix@the-machine poo]$ time ./virtual-unoptimized 

real    0m32.386s
user    0m32.111s
sys 0m0.010s
[felix@the-machine poo]$ time ./virtual-O2

real    0m26.875s
user    0m26.668s
sys 0m0.003s
[felix@the-machine poo]$ time ./virtual-O3

real    0m26.905s
user    0m26.645s
sys 0m0.017s

未优化在虚拟时仍然快 1 秒,我觉得这有点奇怪。但这是一个很好的实验,感谢大家的回答!

【问题讨论】:

  • 你能用-O3(3级优化)试试吗?
  • 我认为您要查找的术语称为 vtable
  • 啊,又一个微基准测试!可爱的,我开始想念他们了……
  • 我可以通过向其中添加代码来加速循环。也从来没有得到一个好的答案。也许有一些关于现代处理器的事情是人类从未想过的? stackoverflow.com/questions/688325/…

标签: c++ oop virtual


【解决方案1】:

分析未优化的代码几乎没有意义。使用-O2 产生有意义的结果。使用-O3 可能会产生更快的代码,但它可能不会产生实际的结果,除非您将A::fB::f 分别编译为main(即在不同的编译单元中)。

根据反馈,甚至-O2 也可能过于激进。 2 毫秒的结果是因为编译器完全优化了循环。直接呼叫并没有那么快;事实上,应该很难观察到任何明显的差异。将f 的实现移动到单独的编译单元中以获取实数。在 .h 中定义类,但在它们自己的 .cc 文件中定义 A::fB::f

【讨论】:

  • 天哪! -O2-O3 都将 normal 版本减少到 0.002 秒,而 virtual 保持在 ~27 秒。
  • @Felix 优化器可能已经完全删除了函数调用(可能还有循环)——你必须使函数具有全局副作用以防止这种情况发生。
  • @Felix:请注意,这也不代表普通虚函数调用的开销:在normal 的情况下,可能所有函数调用和整个循环都被优化掉了。
  • 如果您想要更真实的场景,请将A::fB::f 的定义移动到单独的文件中。这样,代码生成器将不知道这些函数做了什么,而必须生成实际调用(除非您启用了链接时间/整个程序优化)。
  • @Felix:优化器优化了非虚拟调用,甚至完全消除了 for 循环。实际上,虚拟调用并不比非虚拟调用慢多少(例如,调用空虚函数应该比将 int 除以 float 更快)。
【解决方案2】:

一旦 vtable 在缓存中,实际做某事的虚拟函数和非虚拟函数之间的性能差异非常小。在使用 C++ 开发软件时,这当然不是您通常应该关心的事情。正如其他人所指出的,在 C++ 中对未优化的代码进行基准测试毫无意义。

【讨论】:

  • +1 在现实世界中,@Felix,担心这样的事情称为 微优化,而不是优秀的程序员浪费时间做的事情 - 编写大型系统已经够难了。您真正担心虚拟调用速度(和其他微优化)的唯一时间是您每秒调用该函数数百万次。
【解决方案3】:

鉴于程序的简单性,编译器很有可能会优化掉某些东西,或者类似的东西。增加复杂性/使编译器准确地编译你想要的东西是你应该针对这种事情的目标(在运行时,我相信只有 2 个取消引用,所以少于函数调用的其余部分)。一种方法是,正如@Marcelo 所说,将 A 和 B 编译到与 main 不同的文件中——我会更进一步,将每个文件编译到自己的文件中。然而,我不同意他的观点,因为上述原因,您应该关闭优化,以便编译器生成您的代码的字面翻译,并且不会删除任何内容。

【讨论】:

    【解决方案4】:

    考虑到 CPU 在重新组织代码、交叉计算和内存访问方面做了多少工作,我不会过多解读 4% 的差异 - 因为您无法得出任何明智的结论,所以不值得担心从这样的微基准测试。

    尝试使用真正的内存访问进行真正的计算,以了解虚拟方法的成本是多少。虚方法本身通常不是问题——现代 cpu 将从 vtable 中获取指针与其他工作交错——缺乏内联会降低性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-10-28
      • 1970-01-01
      • 1970-01-01
      • 2016-10-28
      • 2011-08-29
      • 2011-04-19
      相关资源
      最近更新 更多