C++ std::bind绑定成员函数调用性能远低于直接调用原因排查
std::bind+std::function调用成员函数性能异常问题 问题描述
通过this->UpdateB = std::bind(&Test::Update, this);绑定类成员方法后,使用test.UpdateB()发起调用的整体性能,远低于直接调用test.Update()的性能,且这种性能下降还会影响方法内部逻辑的执行效率。
性能测试结果如下:
测试代码如下:
#include <functional> #include <benchmark/benchmark.h> typedef unsigned u32; typedef uint64_t u64; constexpr auto nP = nullptr; constexpr bool _F = false; constexpr bool _T = true; constexpr u64 HIGH_LOAD = 1000000000; constexpr u64 LOW_LOAD = 10; struct Test { u32 counter{100000}; u64 soak{0}; u64 load{10}; bool isAlive{_T}; std::function<bool()> UpdateB; Test() { UpdateB = std::bind( &Test::Update, this); } bool Update() { if (counter > 0) { counter --; } u64 i = load; while(i--) { soak += 1; } isAlive = counter > 0; return isAlive; } }; static void DirectCallLowLoad(benchmark::State& state) { Test test; test.load = LOW_LOAD; for (auto _ : state) { test.Update(); } } BENCHMARK(DirectCallLowLoad); static void DirectCallHighLoad(benchmark::State& state) { Test test; test.load = HIGH_LOAD; for (auto _ : state) { test.Update(); } } BENCHMARK(DirectCallHighLoad); static void BoundCallLowLoad(benchmark::State& state) { Test test; test.load = LOW_LOAD; for (auto _ : state) { test.UpdateB(); } } BENCHMARK(BoundCallLowLoad); static void BoundCallHighLoad(benchmark::State& state) { Test test; test.load = HIGH_LOAD; for (auto _ : state) { test.UpdateB(); } } BENCHMARK(BoundCallHighLoad);
初始性能预期
BoundCallHighLoad的性能应当接近DirectCallHighLoad,方法内部负载较高时调用开销的占比会降低- 低负载场景下的直接调用性能应当显著优于高负载场景下的直接调用,绑定调用也应当符合该规律
- 绑定调用的性能不应该比直接调用慢近5倍
额外测试现象
如果改用如下写法:
std::function<bool(Test*)> UpdateB; Test() { UpdateB = &Test::Update; } // 构造函数内赋值 // 调用方式 test.UpdateB(&test);
性能表现反而更差:test.UpdateB(&test)的调用速度比直接调用test.Update()慢数个数量级,且方法内部的处理负载大小几乎不会对该性能差距产生影响。
问题解答
测试代码是否有问题?
测试逻辑本身没有错误,性能差距来自std::function的固有特性和编译器优化行为的差异。
绑定调用性能差的核心原因
std::function的类型擦除固有开销std::function是基于类型擦除实现的通用可调用对象包装器,它可以包装任意签名匹配的可调用对象(函数、lambda、绑定表达式、自定义函数对象等),因此每次调用都需要走间接跳转逻辑:调用时需要先访问内部存储拿到被包装对象的地址,再通过函数指针发起间接调用,本身就比直接调用已知地址的成员函数多了一层固定开销。- 编译器优化被严重阻碍
直接调用test.Update()时,编译器在开启O2/O3优化的前提下,可以很轻松地做激进优化:比如把Update函数完全内联到调用循环中,把内部的while(i--)循环直接优化为soak += load的常数计算,完全消除循环和函数调用开销。
但通过std::function调用时,编译器无法跨类型擦除层确认内部包装的可调用对象就是Test::Update——理论上std::function可以在运行时被替换为任意同签名的其他可调用对象,因此编译器不敢贸然做内联、循环展开、常数计算这类优化,只能生成最保守的调用和循环代码。这部分优化缺失带来的性能损失远大于函数调用本身的开销,甚至会让方法内部的循环执行效率下降,也就是观察到的“性能下降影响方法内部逻辑执行效率”的现象。 - 高负载场景下开销未被摊薄的原因
预期高负载下调用开销占比会降低,但实际因为优化被阻碍,高负载的内部逻辑本身就没有被优化到最优:直接调用时10亿次累加的循环会被编译器直接优化为常数加法,几乎没有运行时开销;但通过std::function调用时,编译器只能老老实实生成循环递减、判断、累加的指令,这部分的性能差距远大于单次调用的开销,因此哪怕负载很高,性能差距依然非常明显。 - 第二种写法性能更差的原因
把成员函数指针直接存入std::function<bool(Test*)>的写法,会叠加两层开销:一是成员函数指针本身调用需要处理this偏移、虚函数适配的固有逻辑,比普通函数调用开销更高;二是std::function的类型擦除间接跳转开销。同时这种写法下编译器依然无法跨类型擦除层做内联优化,内部循环的优化同样被阻碍,因此负载高低几乎不会影响性能差距,整体性能比绑定this的std::function版本更差。
优化建议
如果追求极致调用性能,不要用std::function存储固定的类成员函数:
- 可以直接存储成员函数指针,调用时传入this指针,编译器可以对这类调用做更好的优化
- 如果需要捕获上下文,优先使用同签名的lambda直接定义为已知类型,避免类型擦除开销
- C++20环境下可以用
std::move_only_function替代std::function,能减少一部分小对象优化的开销,但依然无法解决跨类型擦除层无法内联的问题 - 调用点和绑定点在同一个编译单元、可调用对象固定的场景下,直接调用永远是性能最优的选择
内容的提问来源于stack exchange,提问作者Blindman67
相关产品推荐
相关产品推荐

