为何轻量函数中短参数传值比传const引用更慢?
短结构体传值vs传const&的性能反常现象分析
背景与测试代码
常规观点认为,短参数(sizeof(X) <= 16)应采用传值而非const&方式传递,因为值可通过寄存器传递,而引用以指针实现,会强制内存访问。我通过一个简短测试量化该差异:
结构体定义(struct.h)
struct X { unsigned long fill=0; unsigned long x=0; };
测试函数(functions.cpp)
bool struct_by_value(X x) { return x.x < 17; } bool struct_by_cref(X const& x) { return x.x < 17; }
主测试代码(main.cpp)
int main() { size_t N = 1UL << 32; X x; double diff = 0; auto start = clk::now(); for (unsigned long i; i < N; i++){ struct_by_cref(x); } auto end = clk::now(); std::cout << "cref: " << secs(end-start) << std::endl; start = clk::now(); for (unsigned long i; i < N; i++){ struct_by_value(x); } end = clk::now(); std::cout << "value: " << secs(end-start) << std::endl; start = clk::now(); for (x.x = 0; x.x < N; x.x++){ struct_by_cref(x); } end = clk::now(); std::cout << "cref: " << secs(end-start) << std::endl; start = clk::now(); for (x.x = 0; x.x < N; x.x++){ struct_by_value(x); } end = clk::now(); std::cout << "value: " << secs(end-start) << std::endl; }
编译结果与汇编代码
使用g++-12.2.0 -O2编译后,两个函数的汇编代码如下:
struct_by_value的汇编
_Z15struct_by_value1X: .LFB0: .cfi_startproc cmp rsi, 16 setbe al ret .cfi_endproc
struct_by_cref的汇编
_Z14struct_by_crefRK1X: .LFB1: .cfi_startproc cmp QWORD PTR 8[rdi], 16 setbe al ret .cfi_endproc
可以看到struct_by_cref存在明显的内存访问操作。
测试结果(i7-10850H机器)
固定结构体的循环测试
以下两个循环的运行时间均约为6.5秒,多次运行的误差大于传值与传引用的性能差异:for (unsigned long i; i < N; i++){ struct_by_cref(x); }和
for (unsigned long i; i < N; i++){ struct_by_value(x); }修改结构体成员的循环测试
以下两个循环的运行时间分别约为7.2秒(传引用)和7.4秒(传值),传值调用始终比传引用慢约0.2秒:for (x.x = 0; x.x < N; x.x++){ struct_by_cref(x); }和
for (x.x = 0; x.x < N; x.x++){ struct_by_value(x); }
疑问与初步分析
第一个测试的原因不难解释:结构体从未改变,长且紧凑的循环中分支预测效果良好,加载操作可提前执行,且内存吞吐量和执行端口远未饱和,因此传值与传const引用之间没有性能差异。
但第二个测试令人困惑:
- 传const引用调用时,每次迭代中
x.x需在main函数中写入内存,再在struct_by_cref中读取; - 传值调用中整个结构体理论上可驻留在寄存器中,无需内存操作。
由于值刚写入内存就被读取,无法进行预取。是否存在CPU将连续的内存存/取操作替换为寄存器间移动的优化?但为何传值版本反而更慢?按常理它本应全程使用寄存器。
内容的提问来源于stack exchange,提问作者loreson
相关产品推荐
相关产品推荐

