探究std::function与捕获同步lambda的性能开销差异原因
为什么非模板版本的std::function性能远低于模板版本?
先看测试代码(使用GCC11 O3编译):
#include <QCoreApplication> #include <QDebug> #include <QElapsedTimer> #include <QRandomGenerator> #include <QDateTime> #include <functional> inline void reader(int * d, int c, std::function<void(int *)> foo) { while (c--) foo(d++); } template <class FOO> inline void readerT(int * d, int c, FOO foo) { while (c--) foo(d++); } inline int summer(int * d, int c) { int s = 0; reader(d, c, [&s](int * dd){ s += *dd; }); return s; } inline int summerT(int * d, int c) { int s = 0; readerT(d, c, [&s](int * dd){ s += *dd; }); return s; } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); int tcount = 100000000; QVector<int> vec; vec.resize(tcount); QRandomGenerator gen = QRandomGenerator::securelySeeded(); gen.generate(vec.begin(), vec.end()); QElapsedTimer t; t.restart(); qDebug() << summer(vec.data(), tcount) << t.elapsed(); // 110 ms t.restart(); qDebug() << summerT(vec.data(), tcount) << t.elapsed(); // 15 ms t.restart(); int sum = 0; for (const auto & i : vec) { sum += i; } qDebug() << sum << t.elapsed(); // 15 ms return a.exec(); }
测试结果显示:非模板的summer函数耗时110ms,而模板版本summerT和普通循环的耗时都是15ms,性能差距悬殊。核心原因在于std::function的类型擦除机制带来的开销,以及编译器无法对其进行有效内联优化:
- 类型擦除的间接调用开销:
std::function是通用的可调用对象容器,通过类型擦除兼容任意符合签名的可调用实体(lambda、函数指针、functor等)。内部依赖虚函数表或函数指针间接调用目标函数,循环中每次foo(d++)都需要一次间接跳转,无法像模板版本那样直接内联lambda逻辑。 - 模板的编译期具体化与内联:模板函数
readerT编译时会根据传入的lambda类型生成具体实例,编译器完全知晓lambda实现细节,可直接把lambda代码内联到readerT的循环中,甚至进一步将整个readerT逻辑内联到summerT里,最终生成的机器码和普通循环几乎一致,实现“零开销”。 - 额外的存储与内存操作:
std::function可能需要为存储可调用对象分配内存(即使小对象优化,也有额外存储成本和构造/析构操作),而模板版本直接传递lambda的副本或引用,无此类额外开销。
简单来说,模板版本是编译期确定的静态分发,std::function是运行期的动态分发——后者的灵活性是以性能为代价的,需要通用兼容性时用std::function,追求极致性能时模板是更优选择。
内容的提问来源于stack exchange,提问作者dtech
相关产品推荐
相关产品推荐

