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

为何迭代调用双参数std::max比initializer_list版本速度更快

测试环境

编译环境:Visual Studio 2019,优化选项:优先速度(/O2)

测试场景

在执行100万次循环的测试场景中,使用std::max求解10个元素的最大值。
第一种采用迭代嵌套方式调用双参数std::max,代码如下:

using namespace std;
using namespace chrono;
auto start = high_resolution_clock::now();
for(int i=0;i<1000000;i++)
    out = max(arr[i],max(arr2[i],max(/* 剩余参数嵌套传入 */)));
auto end= high_resolution_clock::now();
cout << duration_cast<milliseconds>(end-start).count()<<endl;

该写法的运行速度远快于第二种写法:

using namespace std;
array<int,10> arrs;
auto start = high_resolution_clock::now();
for(int i=0;i<1000000;i++)
    {
    arrs = {arr[i],arr2[i],/* 剩余元素依次填入 */};
    out = max(arrs);
    }
auto end= high_resolution_clock::now();
cout << duration_cast<milliseconds>(end-start).count()<<endl;
核心疑问

造成该性能差异的本质原因是什么?即为什么双参数std::max重载版本:

constexpr const T& max( const T& a, const T& b );

运行速度远快于接收std::initializer_list参数的重载版本:

template< class T >
constexpr T max( std::initializer_list<T> ilist );
性能差异原因
  • 双参数版本无额外内存开销:双参数std::max仅接收两个元素的常量引用,嵌套调用全程不会产生临时对象,不需要额外申请、写入内存。开启/O2优化后,编译器会直接把多层嵌套调用完全展开,生成一连串原生CPU比较指令,参与比较的值甚至可以全程放在寄存器中操作,完全不需要访问栈内存,运行效率和手写原生比较代码几乎没有差距。
  • initializer_list版本天生带不可消除的基础开销:调用该重载时,编译器首先要在栈上开辟一块连续的临时内存,把传入的所有元素逐个拷贝到这块内存中,再通过initializer_list内部存储的首尾指针走循环遍历完成比较。即使优化等级拉满,这一步临时内存写入、指针遍历的开销也很难完全抹除。上述第二个测试用例还额外构造了std::array、做了一次数组整体赋值,比直接传入initializer_list还多了一次整块内存拷贝的开销,速度差距会更明显。
  • 两者的编译期优化难度差距极大:10个元素用双参数嵌套调用时,比较次数、执行逻辑在编译期是完全固定的(固定比较9次,无分支跳转的额外不确定性),编译器可以很轻松地完成指令重排、分支预测优化,如果输入是编译期常量甚至能直接算出最终结果。而initializer_list版本的底层实现是循环遍历,VS2019搭载的MSVC编译器对这种短循环的展开优化能力远弱于显式嵌套调用,很难完全消掉循环计数、边界判断的额外指令开销。
  • 返回值实现差异也会放大性能差距:双参数版本返回的是输入参数的常量引用,不需要做值拷贝;initializer_list版本默认返回T类型的值,对int这类基础类型影响不大,但如果是自定义复杂类型,还会额外产生一次拷贝构造开销,进一步拉大两者的速度差。

内容的提问来源于stack exchange,提问作者Muhammet Dabak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:33:23