在汇编中,如果我有一个JUMP表,其地址超过2000个标签:
.TABLE:
DD .case0
DD .case1
DD .case2
DD .case3
DD .case4
...
...
...
DD .case2000
哪种方法更适合跳转:
方式1:
mov r12d, .TABLE ; r12d or any other registers
mov ebx, [r13d] ; r13d holds the id of case * 4 so we don't need to '4 * ebx'
add ebx, r12d ; ebx = address for Jumping
jmp ebx
方式2 :(与方式1相同,但'add ebx, r12d'
被删除并更改为'jmp [ebx+r12d]'
)
mov r12d, .TABLE ; r12d or any other registers
mov ebx, [r13d] ; r13d holds the id of case * 4 so we don't need to '4 * ebx'
jmp [ebx+r12d]
方式3:
mov ebx, [r13d] ; r13d holds the id of case * 4 so we don't need to '4 * ebx'
jmp [ebx + .TABLE]
在“方法1”中,由于额外的功能,我们遇到了源代码大小问题,但是我认为它比其他方法具有更好的性能,因为我将进行约2000次跳转(不规则跳转(可能是case0到case1000或...)
因此,对于跳跃性能,在具有大量JUMP的源代码中哪种方法更好?
如果您不使用32位地址大小来压缩跳转表,而对于64位模式使用qword指针,则是一个很好的选择。
否则,您将希望加载16位或32位偏移(movzx
或mov
),并从相对RIP的LEA中为64位代码添加到一些64位基址。 (这也使其与位置无关)。
最少的说明并不总是解决方案!
但是在这种情况下,最少的指令也是最少的指令。 [disp32 + reg]
寻址模式非常有效。
如果要考虑使用更多指令,则将指针加载到jmp reg
的寄存器中,而不是使用jmp [mem]
,而不是进一步简化寻址模式。
https://agner.org/optimize/显示Intel Sandybridge家族上的jmp mem
仍然只有1个融合域uop,负载微融合到端口6跳uop中。因此,单独的mov
负载实际上将在前端花费more uops。
(索引寻址模式可能会取消分层; jmp [.TABLE + ebx*4]
在问题发布/重命名阶段将花费2块,但在解码器和uop缓存中仍然只有1块。但是由于某种原因,您似乎在内存中存储了字节偏移,因此您不需要缩放索引。)