模拟编译器,手动优化虚函数(顺便学习perf和虚函数)
参考来源 :虚函数调用性能提高一倍的方法
(实际意义不大,主要是学习编译器优化的一种手段和虚函数)
这里先创造了一个环境——两个类
-
基类A的虚函数update啥也没干
-
子类B的虚函数update将count++;
virtual void update() override { count++; }
然后循环100000 运行9999个A的update 和 1 个B的update进行时间比较
如果是编译器优化(-O2)则不会运行A的update,节省了函数调用的开销
那么编译器是如何优化的呢?
perf 快捷安装 unbuntu
sudo apt install linux-tools-common
sudo apt-get install linux-tools-4.15.0-128-generic
通过perf工具运行下面的第一种情况(vector全是A,注释掉B类,编译器自动优化)可以进行查看,
发现编译器 是通过比较 指针所指向对象的update是否和父类一致进行判断的,是否进行优化的。
查看方式
sudo perf record ./a.out
sudo perf report
4种情况程序运行时间
- vector全是A,不注释掉B类,无优化 —— 最慢
- vector里有个B,无优化 —— 最慢
- vector全是A,注释掉B类,编译器自动优化 —— 耗费时间是情况2的一半
- vector里有个B,手动开启优化(不注释if语句,第69行) —— 耗费时间是情况2的一半
完整代码
#include <chrono>
#include <iostream>
#include <vector>
// 获取虚函数偏移量
template <typename T>
int get_function_vtable_offset(T t)
{
union
{
T ptr;
int offset;
};
ptr = t;
return (offset - 1); // 偏移量,(offset - 1)/ sizeof(void*)
}
class A;
// 获取虚表
void* get_virtual_function_address(const A* a, int offset)
{
void** vtable = *(void***)a; // 强转为 void***,在取*,虚函数指针在 类的头部,变为void**
return vtable[offset];
}
class A
{
public:
void init()
{
static const int offset = get_function_vtable_offset(&A::update); // update在虚函数表的偏移量
static const A proto_type;
static const void* a_update_address = get_virtual_function_address(&proto_type, offset); // a的虚函数地址
has_update = a_update_address != get_virtual_function_address(this, offset); // b的虚函数地址
}
virtual void update(){} // 基类虚函数
bool has_update = false; // 有没有重载
};
static int count = 0;
class B : public A
{
public:
virtual void update() override
{
count++;
}
};
int main()
{
const int M = 10000;
const int N = 100000;
std::vector<A*> va;
va.reserve(M);
for (int i = 0; i < M - 1; i++)
{
va.push_back(new A());
va.back()->init();
}
va.push_back(new B());
va.back()->init();
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) // 循环100000次
{
for (int j = 0; j < M; j++)
{
// if (va[j]->has_update) // 判断是否是子类 // 自行注释,是否开启优化
va[j]->update();
}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << count << '\t'<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
return 0;
}