cmp r14, qword ptr [rbx+8]是否比cmp rbx,r14更慢?循环性能疑问
两种汇编循环的性能差异分析
我想对比以下两个汇编循环是否存在性能差异:
.LOOP1: mov edi, dword ptr [rbx] call f(int)@PLT add rbx, 4 cmp rbx, r14 jne .LOOP1
.LOOP2: mov edi, dword ptr [r14] call f(int)@PLT add r14, 4 cmp r14, qword ptr [rbx + 8] jne .LOOP2
两者的核心差异在于cmp指令:假设LOOP1中寄存器间的cmp仅耗时1周期,LOOP2中需要读取内存[rbx + 8]的cmp是否会耗时超过1周期?
补充说明:这段代码来源于C++ vector的两种元素访问方式,仅从cmp指令的性能差异来看,我认为范围for循环的表现略优。
对应的C++代码:
#include <vector> using namespace std; int f(int); void range(const vector<int> &input) { for (int i : input) { f(i); } } void iter(const vector<int> &input) { for (auto i = input.begin(); i != input.end(); ++i) { f(*i); } }
gcc -O3编译后的汇编代码:
range(std::vector<int, std::allocator<int> > const&): push rbp push rbx sub rsp, 8 mov rbx, QWORD PTR [rdi] mov rbp, QWORD PTR [rdi+8] cmp rbp, rbx je .L1 .L3: mov edi, DWORD PTR [rbx] add rbx, 4 call f(int) cmp rbp, rbx jne .L3 .L1: add rsp, 8 pop rbx pop rbp ret iter(std::vector<int, std::allocator<int> > const&): push rbp push rbx sub rsp, 8 mov rbx, QWORD PTR [rdi] cmp rbx, QWORD PTR [rdi+8] je .L7 mov rbp, rdi .L9: mov edi, DWORD PTR [rbx] add rbx, 4 call f(int) cmp QWORD PTR [rbp+8], rbx jne .L9 .L7: add rsp, 8 pop rbx pop rbp ret
性能差异分析
- LOOP1的寄存器间比较:
cmp rbx, r14是寄存器到寄存器的操作,现代CPU微架构下这类指令延迟通常为1周期,且能被流水线完全吸收,没有额外内存开销。 - LOOP2的寄存器-内存比较:
cmp r14, qword ptr [rbx + 8]需要先从内存地址rbx+8加载数据到临时寄存器,再执行比较:- 若该地址已在L1缓存中,内存加载延迟约3-4周期(不同CPU略有差异),加上比较的1周期,总耗时明显超过1周期;
- 若缓存未命中,需要从L2/L3或主存加载,耗时会大幅增加,但vector的end地址属于容器头部数据,大概率会被高频访问并留在L1缓存中,因此主要额外开销是L1加载延迟。
结合C++代码的对应逻辑:
- 范围for循环生成的
range函数,提前将vector的end地址加载到rbp寄存器,循环内仅用寄存器比较,避免了每次循环的内存读取; - 迭代器循环生成的
iter函数,每次循环都要重新读取内存中的end地址,即使缓存命中,也会带来持续的额外延迟。
结论
在循环次数较多的场景下,范围for循环生成的汇编代码确实会比这类迭代器循环略优,累计的延迟差异会体现出性能差距。
内容的提问来源于stack exchange,提问作者A. K.
相关产品推荐
相关产品推荐

