assembly - 为什么 "inc dword [esp + ebx]"比 "inc [esp]"快?

标签 assembly optimization x86

我有以下 NASM 汇编程序,运行时间约为 9.5 秒:

section .text
global _start

_start:
  mov eax, 0
  mov ebx, 8
  loop:
    inc dword [esp + ebx]
    inc eax
    cmp eax, 0xFFFFFFFF
    jne loop

  mov eax, 1
  mov ebx, 0
  int 0x80

但是,如果我将 [esp + ebx] 替换为 [esp + 8] (自 ebx = 8 以来的相同内存位置),甚至只是 [esp ],运行时间为 10.1 秒...

这怎么可能? [esp] 不是比 [esp + ebx] 更容易让 CPU 计算吗?

最佳答案

您没有对齐循环。
如果所有跳转指令与循环的其余部分不在同一高速缓存行中,则会产生额外的周期来获取下一个高速缓存行。

您列出的各种替代方案组装成以下编码。

0:  ff 04 1c                inc    DWORD PTR [esp+ebx*1]
3:  ff 04 24                inc    DWORD PTR [esp]
6:  ff 44 24 08             inc    DWORD PTR [esp+0x8] 

[esp][esp+reg] 均以 3 个字节进行编码,[esp+8] 占用 4 个字节。 由于循环在某个随机位置开始,因此额外的字节将(部分)jne循环指令推送到下一个缓存行。

缓存行通常为 16 字节。

您可以通过重写代码来解决此问题,如下所示:

  mov eax, 0
  mov ebx, 8
  .align 16             ;align on a cache line.
  loop:
    inc dword ptr [esp + ebx]                 ;7 cycles
    inc eax                                   ;0 latency drowned out by inc [mem]
    cmp eax, 0xFFFFFFFF                       ;0   "          "
    jne loop                                  ;0   "          "

  mov eax, 1
  mov ebx, 0
  int 0x80

此循环每次迭代应执行 7 个周期。

忽略循环没有做任何有用工作的事实,它可以进一步优化,如下所示:

  mov eax, 1      ;start counting at 1
  mov ebx, [esp+ebx]
  .align 16
  loop:         ;latency   ;comment
    lea ebx,[ebx+1]  ; 0   ;Runs in parallel with `add`
    add eax,1        ; 1   ;count until eax overflows
    mov [esp+8],ebx  ; 0   ;replace a R/W instruction with a W-only instruction   
    jnc loop         ; 1   ;runs in parallel with `mov [mem],reg`

  mov eax, 1
  xor ebx, ebx
  int 0x80

这个循环每次迭代应该需要 2 个周期。

通过将 inc eax 替换为 add 并将 inc [esp] 替换为不改变您允许的标志的指令CPU 并行运行 lea + movadd+jmp 指令。
add is 在较新的 CPU 上可能会更快,因为 add 会更改所有标志,而 inc 仅更改标志的子集。
这可能会导致 jxx 指令上的部分寄存器停顿,因为它必须等待对标志寄存器的部分写入得到解决。 mov [esp] 也更快,因为您没有执行读取-修改-写入循环,您只是在循环内写入内存。

通过展开循环可以获得更多 yield ,但 yield 会很小,因为这里的内存访问主导了运行时,而这从一开始就是一个愚蠢的循环。

总结一下:

  • 避免循环中的读取-修改-写入指令,尝试用单独的读取、修改和写入指令替换它们,或者将读取/写入移到循环之外。
  • 避免 inc 操作循环计数器,而是使用 add
  • 当您对标志不感兴趣时​​,尝试使用 lea 进行添加。
  • 始终在缓存行上对齐小循环.align 16
  • 请勿在循环内使用 cmpinc/add 指令已更改标志。

关于assembly - 为什么 "inc dword [esp + ebx]"比 "inc [esp]"快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41411045/

相关文章:

macos - 向 x86-64 二进制文件写入跳转命令

performance - go 中 ASM 函数调用的开销

gcc - ARM 内联约束

x86 - 使用 GRUB2 引导非多重引导内核

javascript - 使用类或 jQuery 数据在 dom 元素上存储数据更快吗

assembly - 哪些 CPU 支持 MOVBE 指令?

assembly - 如何在 MacOSX 上使用 nasm 进行编译

c - 编写固件:组装还是高级?

c - 为什么启用优化后 GCC 11 编译器会产生奇怪的输出?

python - 使用两个 DataFrame 的 Pandas groupby 总和