x86-64 相对 jmp 性能
x86-64 Relative jmp performance
我目前正在做一项测量各种 x86-64 命令(at&t 语法)性能的作业。
我有点困惑的命令是 "unconditional jmp" 命令。我是这样实现的:
.global uncond
uncond:
.rept 10000
jmp . + 2
.endr
mov 000, %rax
ret
这很简单。该代码创建了一个名为 "uncond" 的函数,它使用 .rept 指令调用 jmp 命令 10000 次,然后将 return 值设置为您调用 jmp 命令的次数。
“。”在 at&t 语法中表示当前地址,我将其增加 2 个字节以说明 jmp 指令本身(因此 jmp . + 2 应该简单地移动到下一条指令)。
我没有显示的代码计算处理 10000 个命令所需的周期数。
我的结果表明 jmp 非常慢(处理单个 jmp 指令需要 10 个周期)- 但根据我对流水线的理解,无条件跳转应该非常快(没有分支预测错误)。
我错过了什么吗?我的代码有错吗?
CPU没有针对无操作jmp
指令进行优化,所以它不处理继续解码的特殊情况,只是跳转到下一个 insn 的流水线 jmp 指令。
不过,CPUs 针对循环进行了优化。 jmp .
将 运行 在许多 CPU 上每个时钟一个 insn,或者在某些 CPU 上每 2 个时钟一个 insn。
跳转会在指令获取中产生气泡。一个预测良好的跳跃是可以的,但是 运行 只跳跃是有问题的。我在 core2 E6600(Merom/Conroe 微架构)上重现了你的结果:
# jmp-test.S
.globl _start
_start:
mov 0000, %ecx
jmp_test:
.rept 10000
jmp . + 2
.endr
dec %ecx
jg jmp_test
mov 1, %eax
xor %ebx,%ebx
syscall # exit_group(0)
构建和 运行 使用:
gcc -static -nostartfiles jmp-test.S
perf stat -e task-clock,cycles,instructions,branches,branch-misses ./a.out
Performance counter stats for './a.out':
3318.616490 task-clock (msec) # 0.997 CPUs utilized
7,940,389,811 cycles # 2.393 GHz (49.94%)
1,012,387,163 instructions # 0.13 insns per cycle (74.95%)
1,001,156,075 branches # 301.679 M/sec (75.06%)
151,609 branch-misses # 0.02% of all branches (75.08%)
3.329916991 seconds time elapsed
来自另一个运行:
7,886,461,952 L1-icache-loads # 2377.687 M/sec (74.95%)
7,715,854 L1-icache-load-misses # 2.326 M/sec (50.08%)
1,012,038,376 iTLB-loads # 305.119 M/sec (75.06%)
240 iTLB-load-misses # 0.00% of all iTLB cache hits (75.02%)
(每行末尾 (%) 中的数字是计数器处于活动状态的总 运行 时间中的多少:perf
必须在您要求时为您多路复用计算的东西比 HW 一次可以计算的还要多。
所以这实际上不是 I-cache 未命中,它只是指令 fetch/decode 由不断跳转引起的前端瓶颈。
我的 SnB 机器坏了,所以我无法在上面测试数字,但是每次 jmp 8 个周期的持续吞吐量非常接近您的结果(可能来自不同的微体系结构)。
有关详细信息,请参阅 http://agner.org/optimize/, and other links from the x86 标签 wiki。
我目前正在做一项测量各种 x86-64 命令(at&t 语法)性能的作业。
我有点困惑的命令是 "unconditional jmp" 命令。我是这样实现的:
.global uncond
uncond:
.rept 10000
jmp . + 2
.endr
mov 000, %rax
ret
这很简单。该代码创建了一个名为 "uncond" 的函数,它使用 .rept 指令调用 jmp 命令 10000 次,然后将 return 值设置为您调用 jmp 命令的次数。
“。”在 at&t 语法中表示当前地址,我将其增加 2 个字节以说明 jmp 指令本身(因此 jmp . + 2 应该简单地移动到下一条指令)。
我没有显示的代码计算处理 10000 个命令所需的周期数。
我的结果表明 jmp 非常慢(处理单个 jmp 指令需要 10 个周期)- 但根据我对流水线的理解,无条件跳转应该非常快(没有分支预测错误)。
我错过了什么吗?我的代码有错吗?
CPU没有针对无操作jmp
指令进行优化,所以它不处理继续解码的特殊情况,只是跳转到下一个 insn 的流水线 jmp 指令。
CPUs 针对循环进行了优化。 jmp .
将 运行 在许多 CPU 上每个时钟一个 insn,或者在某些 CPU 上每 2 个时钟一个 insn。
跳转会在指令获取中产生气泡。一个预测良好的跳跃是可以的,但是 运行 只跳跃是有问题的。我在 core2 E6600(Merom/Conroe 微架构)上重现了你的结果:
# jmp-test.S
.globl _start
_start:
mov 0000, %ecx
jmp_test:
.rept 10000
jmp . + 2
.endr
dec %ecx
jg jmp_test
mov 1, %eax
xor %ebx,%ebx
syscall # exit_group(0)
构建和 运行 使用:
gcc -static -nostartfiles jmp-test.S
perf stat -e task-clock,cycles,instructions,branches,branch-misses ./a.out
Performance counter stats for './a.out':
3318.616490 task-clock (msec) # 0.997 CPUs utilized
7,940,389,811 cycles # 2.393 GHz (49.94%)
1,012,387,163 instructions # 0.13 insns per cycle (74.95%)
1,001,156,075 branches # 301.679 M/sec (75.06%)
151,609 branch-misses # 0.02% of all branches (75.08%)
3.329916991 seconds time elapsed
来自另一个运行:
7,886,461,952 L1-icache-loads # 2377.687 M/sec (74.95%)
7,715,854 L1-icache-load-misses # 2.326 M/sec (50.08%)
1,012,038,376 iTLB-loads # 305.119 M/sec (75.06%)
240 iTLB-load-misses # 0.00% of all iTLB cache hits (75.02%)
(每行末尾 (%) 中的数字是计数器处于活动状态的总 运行 时间中的多少:perf
必须在您要求时为您多路复用计算的东西比 HW 一次可以计算的还要多。
所以这实际上不是 I-cache 未命中,它只是指令 fetch/decode 由不断跳转引起的前端瓶颈。
我的 SnB 机器坏了,所以我无法在上面测试数字,但是每次 jmp 8 个周期的持续吞吐量非常接近您的结果(可能来自不同的微体系结构)。
有关详细信息,请参阅 http://agner.org/optimize/, and other links from the x86 标签 wiki。