如今,将 CPU 寄存器视为数组确实不是一种常见的方法。我知道的最后一个允许这样做的架构是 PDP11,它在 80 年代末就消失了。为什么不像其他数组一样将数组放入某个内存位置?
也就是说,您可以使用计算跳跃。这还用控制依赖项替换了数据依赖项(索引寻址模式),因此乱序执行程序在开始运行使用最终 RAX 的代码之前不必等待索引输入准备好。当然这假设correct分支预测,如果索引经常变化,则不太可能。分支错误预测会花费许多周期执行很少的工作,但 L1d 缓存中加载的小延迟很容易与独立工作重叠。
吞吐量成本比内存中的数组要高:一些地址计算、一跳、一移动和ret
,而不仅仅是一个mov
甚至是具有索引寻址模式的内存操作数。
要内联此代码,只需替换jmp *%rax
with a call *%rax
,又花费了一个 uop。或者更换ret
指令与jmp
到底部的标签并将跳转表的步幅增加到 8 以考虑更长的编码。
# select a register from r8...r15 according to the value in rdi
select:
lea labels-4*8(%rip),%rax # rdi = 8 is the first jump table entry
lea (%rax,%rdi,4),%rax # pointer to the appropriate entry
jmp *%rax # computed jump
.align 4
labels:
mov %r8, %rax
ret
.align 4
mov %r9, %rax
ret
.align 4
mov %r10, %rax
ret
.align 4
mov %r11, %rax
ret
.align 4
mov %r12, %rax
ret
.align 4
mov %r13, %rax
ret
.align 4
mov %r14, %rax
ret
.align 4
mov %r15, %rax
ret
虽然这可能比三个条件跳转(取决于访问模式)更快,但它肯定不会比仅使用数组更好。
您也可以使用这样的代码,假设索引位于eax
。这是通过将索引位复制到 CF、SF 和 PF 中,然后使用一堆 ALU 操作来区分它们来实现的:
imul $0x4100, %eax, %eax
lahf
# bit 0
mov %r8, %rax
cmovc %r9, %rax
mov %r10, %rcx
cmovc %r11, %rcx
mov %r12, %rdx
cmovc %r13, %rdx
mov %r14, %rbx
cmovc %r15, %rbx
# bit 1
cmovs %rcx, %rax
cmovs %rbx, %rdx
# bit 2
cmovp %rdx, %rax
结果得到在%rax
。由于该代码具有高指令级并行性并且缺乏分支,因此它的性能应该比上面的代码更好,除非索引几乎总是相同。
(被盗自这个答案).