You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 11:45:08