Skip to content

Latest commit

 

History

History
119 lines (87 loc) · 3.29 KB

虚函数为什么慢.md

File metadata and controls

119 lines (87 loc) · 3.29 KB

虚函数为什么那么慢??

正常的函数调用:

  1. 复制栈上的一些寄存器,以允许被调用的函数使用这些寄存器;

  2. 将参数复制到预定义的位置,这样被调用的函数可以找到对应参数;

  3. 入栈返回地址;

  4. 跳转到函数的代码,这是一个编译时地址,因为编译器/链接器硬编码为二进制;

  5. 从预定义的位置获取返回值,并恢复想要使用的寄存器。

而虚函数调用与此完全相同,唯一的区别就是编译时不知道函数的地址,而是:

  1. 从对象中获取虚表指针,该指针指向一个函数指针数组,每个指针对应一个虚函数;

  2. 从虚表中获取正确的函数地址,放到寄存器中;

  3. 跳转到该寄存器中的地址,而不是跳转到一个硬编码的地址。

  • 虚函数通常通过虚函数表来实现,在虚表中存储函数指针,实际调用时需要间接访问,这需要多一点时间。

  • 然而这并不是虚函数速度慢的主要原因,真正原因是编译器在编译时通常并不知道它将要调用哪个函数,所以它不能被内联优化和其它很多优化,因此就会增加很多无意义的指令(准备寄存器、调用函数、保存状态等),而且如果虚函数有很多实现方法,那分支预测的成功率也会降低很多,分支预测错误也会导致程序性能下降。

例如下面就会有虚函数阻碍编译器内联优化和其他优化

#include <cmath>
#include "timer.h"
struct Base {
   public:
    virtual int f(double i1, int i2) { return static_cast<int>(i1 * log(i1)) * i2; }
};

int main() {
    TimerLog t("timer");
    Base *a = new Base();
    int ai = 0;
    for (int i = 0; i < 1000000000; i++) {
        ai += a->f(10, i); // 这里有改动
    }
    cout << ai << endl;
}

//  436 ms
#include <cmath>
#include "timer.h"
struct Base {
   public:
    int f(double i1, int i2) { return static_cast<int>(i1 * log(i1)) * i2; }
};

int main() {
    TimerLog t("timer");
    Base *a = new Base();
    int ai = 0;
    for (int i = 0; i < 1000000000; i++) {
        ai += a->f(10, i); // 这里有改动
    }
    cout << ai << endl;
}

// 154 ms

例如下面优化本身很难的, 二者速度几乎没有区别

#include <cmath>
#include "timer.h"
struct Base {
   public:
    virtual int f(double i1, int i2) { return static_cast<int>(i1 * log(i1)) * i2; }
};

int main() {
    TimerLog t("timer");
    Base *a = new Base();
    int ai = 0;
    for (int i = 0; i < 1000000000; i++) {
        ai += a->f(i, 10); // 这里有改动
    }
    cout << ai << endl;
}

//  12.895s
#include <cmath>
#include "timer.h"
struct Base {
   public:
    int f(double i1, int i2) { return static_cast<int>(i1 * log(i1)) * i2; }
};

int main() {
    TimerLog t("timer");
    Base *a = new Base();
    int ai = 0;
    for (int i = 0; i < 1000000000; i++) {
        ai += a->f(i, 10); // 这里有改动
    }
    cout << ai << endl;
}

// 12.706s