4

在一般情况下,可以占用内存或寄存器操作数的指令怎么会比内存操作数更慢,然后是 mov + mov -> 指令 -> mov + mov

根据在Agner Fog 的指令表中找到的吞吐量和延迟(在我的案例中查看 Skylake,p238),我看到btr/bts指令的以下数字:

instruction, operands, uops fused domain, uops unfused domain, latency, throughput
mov          r,r       1                  1                    0-1      .25
mov          m,r       1                  2                    2        1
mov          r,m       1                  1                    2        .5
... 
bts/btr      r,r       1                  1                    N/A      .5
bts/btr      m,r       10                 10                   N/A      5

我看不出这些数字怎么可能是正确的。即使在最坏的情况下没有可用的寄存器并且您将一个存储在临时内存位置,它会更快:

## hypothetical worst-case microcode that saves/restores a scratch register
mov m,r  // + 1  throughput , save a register
mov r,m  // + .5 throughput , load BTS destination operand
bts r,r  // + 1  throughput , do bts (or btr)
mov m,r  // + 1  throughput , store result
mov r,m  // + .5 throughput , restore register

bts m,r在最坏的情况下,这比(4 < 5)具有更好的吞吐量。(编者注:当它们有不同的瓶颈时,将吞吐量相加是行不通的。您需要考虑 uops 和端口;这个序列应该是 2c 吞吐量,瓶颈是 1/clock 存储吞吐量。)

并且微码指令有自己的一组寄存器,因此看起来不太可能实际需要。任何人都可以解释为什么bts(或通常任何指令)可以比使用最坏情况移动策略具有更高的内存吞吐量,寄存器操作数。

(编者注:是的,微码可以使用一些隐藏的临时寄存器。类似的东西add [mem], reg至少在逻辑上只是加载到其中一个然后存储结果。)

4

1 回答 1

6

您缺少的是 BT、BTC、BTS 和 BTR 在使用内存操作数时不像您描述的那样工作。您假设内存版本与寄存器版本的工作方式相同,但事实并非如此。对于寄存器版本,使用的第二个操作数的值取模 64(或 16 或 32)。对于内存版本,第二个操作数的值按原样使用。这意味着指令访问的实际内存位置可能不是内存操作数给出的地址,而是在它之后的某个位置。

例如,忽略保存寄存器和原子性的需要,要获得与BTS [rsi + rdi], rax使用 BTS 的寄存器版本相同的操作,您需要执行以下操作:

LEA rbx, [rsi + rdi]
MOV rcx, rax
SHR rcx, 8
MOV rdx, [rbx + rcx]
BTS rdx, rax
MOV [rbx + rcx], rdx

如果您知道 RAX 的值小于 64,或者它是一个更简单的内存操作数,则可以简化此操作。事实上,正如您所注意到的,在这种情况下,使用较快的寄存器版本而不是较慢的内存版本可能是一个优势,即使这意味着更多的指令。

于 2020-08-14T04:03:39.413 回答