vector中transform结合back_inserter的性能差异问题排查
为何
std::transform结合back_inserter的性能远低于原始循环? 在排查代码执行效率问题时,定位到对大容量std::vector使用std::transform的语句性能极差,改用原始循环后效率显著提升。以下是测试代码、结果及差异原因分析:
测试代码
#include <iostream> #include <cstdlib> #include <vector> #include <chrono> #include <algorithm> int main() { constexpr auto size = 10000000; constexpr auto divisor = 256U; std::vector<uint16_t> lSource; lSource.reserve(size); for(auto i = 0U; i < size; ++i) { lSource.push_back(std::rand()); } std::vector<uint8_t> lTarget1, lTarget2, lTarget3; { auto start = std::chrono::steady_clock::now(); lTarget1.reserve(size); std::transform( lSource.cbegin(), lSource.cend(), std::back_inserter( lTarget1 ), [divisor]( const uint16_t &arg ) { return arg / divisor; } ); auto end = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( end - start ); std::cout << "time1:" << elapsed.count() << " ms" << std::endl; } { auto start = std::chrono::steady_clock::now(); lTarget2.resize(size); for(auto i = 0U; i < lSource.size(); ++i) { lTarget2[i] = lSource[i] / divisor; } auto end = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( end - start ); std::cout << "time2:" << elapsed.count() << " ms" << std::endl; } { auto start = std::chrono::steady_clock::now(); lTarget3.resize(size); std::transform( lSource.cbegin(), lSource.cend(), lTarget3.begin(), [divisor]( const uint16_t &arg ) { return arg / divisor; } ); auto end = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( end - start ); std::cout << "time3:" << elapsed.count() << " ms" << std::endl; } if(lTarget1!= lTarget2 ) { std::cout << "different result1"; } if(lTarget1!= lTarget3 ) { std::cout << "different result2"; } }
测试结果
初始无优化测试
time1:416 ms (reserve + transform + back_inserter) time2:71 ms (resize + raw loop) time3:166 ms (resize + transform)
不同编译选项下的测试数据
onlinegdb无编译参数: time1:416 ms (reserve + transform + back_inserter) time2:71 ms (resize + raw loop) time3:166 ms (resize + transform) 添加-Ofast: time1:10 ms time2:13 ms time3:8 ms 添加-O2: time1:9 ms time2:11 ms time3:9 ms 本地Visual Studio 2022 /O2: time1:36 ms time2:20 ms time3:8 ms
性能差异原因分析
1. std::back_inserter的额外运行时开销
std::back_inserter本质是通过调用push_back向容器添加元素,即使已经用reserve预分配了足够容量,push_back仍会包含size更新和边界检查逻辑(即使容量足够,部分实现仍会保留最低限度的检查)。这些额外操作在无优化编译时会被放大,处理千万级元素时累计开销非常可观。
而resize后直接通过下标赋值(time2、time3),是直接对已分配内存进行写入操作,没有push_back的额外逻辑,内存访问路径更简洁。
2. 编译器向量化优化的差异
现代编译器会对简单的内存访问模式进行SIMD向量化优化,提升批量数据处理效率:
- 原始循环(time2)和直接写入目标迭代器的
transform(time3)都是连续内存的直接赋值,编译器容易识别这种模式并生成向量化指令; - 结合
back_inserter的transform(time1),由于push_back的逻辑干扰,编译器很难对整个循环进行向量化优化,只能按单元素处理,效率大幅降低。
3. 编译器优化等级的影响
- 无优化(-O0):模板函数(如
back_inserter的operator=、push_back)不会被内联,每次调用都有函数调用开销,进一步拉大性能差距; - 开启-O2/-Ofast:编译器会内联所有相关函数,消除
back_inserter和push_back的额外开销,同时进行向量化优化,此时三种方式的性能差异大幅缩小; - Visual Studio /O2:VS的优化策略与GCC/Clang存在差异,对
back_inserter的向量化支持不如后者,因此time1的性能仍落后于其他两种方式。
内容的提问来源于stack exchange,提问作者David Levy
相关产品推荐
相关产品推荐

