【问题标题】:Virtual function call consistently faster than normal function call. Why?虚函数调用始终比普通函数调用快。为什么?
【发布时间】:2012-06-02 06:18:33
【问题描述】:

我编写了以下程序来测试我的机器上虚拟功能的成本:

#include <iostream>
#include <ctime>
#define NUM_ITER 10000000000
//   5 seconds = 1000000000

static volatile int global_a;

void spin()
{
    int a = global_a;
    int b = a*a;
    int c = a+5;
    int d = a^b^c;
    global_a = b*d;
}

struct A {
    virtual void a() = 0;
};

struct B : A {
    virtual void a() { spin(); }
};

struct C : A {
    virtual void a() { spin(); }
};

void run_A1(A* a)
{
    a->a();
}

void run_A(A* a)
{
    for (long long i = 0; i < NUM_ITER; i++) {
        run_A1(a);
    }
}

void run()
{
    for (long long i = 0; i < NUM_ITER; i++) {
        spin();
    }
}

int main()
{
    global_a = 2;

    A* a1 = new B;
    A* a2 = new C;

    std::clock_t c_begin, c_end;

    c_begin = std::clock();
    run_A(a1);
    c_end = std::clock();

    std::cout << "Virtual | CPU time used: "
              << 1000.0 * (c_end-c_begin) / CLOCKS_PER_SEC
              << " ms\n";

    c_begin = std::clock();
    run_A(a2);
    c_end = std::clock();

    std::cout << "Virtual | CPU time used: "
              << 1000.0 * (c_end-c_begin) / CLOCKS_PER_SEC
              << " ms\n";

    c_begin = std::clock();
    run();
    c_end = std::clock();

    std::cout << "Normal  | CPU time used: "
              << 1000.0 * (c_end-c_begin) / CLOCKS_PER_SEC
              << " ms\n";

    delete a1;
    delete a2;
}

结果与我的预期相反:虚函数始终更快。例如,这是我使用NUM_ITER = 10000000000 得到的输出之一:

Virtual | CPU time used: 49600 ms
Virtual | CPU time used: 50270 ms
Normal  | CPU time used: 52890 ms

通过对生成的汇编文件的分析,我可以确认编译器没有优化出任何重要的东西。我使用 GCC-4.7 和以下选项:

g++ -O3 -std=c++11 -save-temps -masm=intel -g0 -fno-exceptions -fno-inline test.cc -o test

为什么虚函数调用更快?或者为什么非虚拟函数调用更慢?分支预测器变得这么好了吗?或者也许它只是我的机器。也许有人也可以测试并发布他的时间安排?

【问题讨论】:

  • 我无法在 ideone 上重现此内容。
  • MSVS 对于非虚拟呼叫也显示出明显的优势。
  • @Pubby:Ideone 不是测试这一点的最佳选择,因为他们一次在服务器上运行许多程序。
  • @jons34yp 你错了。拨打sleep(1) 加上几个clock() 电话,您会发现很多事情都会使clock() 判断的时间无效。
  • 好吧,这并不能解释一切,因为这只是意味着 spin() 可以内联,而虚拟方法不能。但是即使没有内联,非虚拟版本也应该更快。尝试先运行普通版本而不是最后运行。

标签: c++ performance virtual-functions


【解决方案1】:

在每次调用run()之前尝试重置global_a

void run()
{
    global_a = 2;

    ...
}

void run_A(A *a)
{    
    global_a = 2;

    ...
}

不确定这是否有任何影响,但并非所有数学运算都花费相同的时间!

【讨论】:

  • 这似乎是正确的原因。它与global_a 的值有关,每种情况都不同。我重新排序了呼叫,将非虚拟呼叫放在虚拟呼叫之前,结果令人惊讶:非虚拟呼叫变得更快。这意味着,它与虚拟与非虚拟的关系较小,而与 global_a 的值有关:无论哪个函数首先使用 global_a=2,它似乎也运行得更快。
  • 我无法通过重新排序来影响性能。可以肯定的是,我添加了global_a = 2;,它并没有改变任何东西。 Nawaz,也许你还改变了其他东西?
【解决方案2】:

编译器可能足够聪明,可以看到虚函数调用全局函数spin() 并对其进行去虚拟化。这些调用可能也会被内联。

检查this

【讨论】:

  • 您要么表达得很糟糕,要么不了解去虚拟化的工作原理。当编译器知道变量的实际类型以及将调用哪些虚拟函数时,就会发生去虚拟化。在这种情况下,虚函数由函数run_A1()调用,可以t know the actual type of its parameter, unless it's inlined. Inlining is disabled by -fno-inline`。
  • @jons - 欺骗编译器很难。它理论上仍然可以看到该函数只在一个地方被调用并且总是使用相同的指针。如果我们能看到它,编译器就可以!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
  • 2022-06-12
相关资源
最近更新 更多