Run*_*oro 11 optimization x86 assembly
我有以下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
Run Code Online (Sandbox Code Playgroud)
但是,如果我替换[esp + ebx]
为[esp + 8]
(自ebx = 8以来相同的内存位置)或甚至只是[esp]
,它在10.1秒内运行...
这怎么可能?是不是[esp]
为CPU比计算更容易[esp + ebx]
?
你没有对齐你的循环。
如果所有跳转指令与循环的其余部分不在同一高速缓存行中,则会产生额外的周期来获取下一个高速缓存行。
您列出的各种替代方案组装成以下编码。
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]
Run Code Online (Sandbox Code Playgroud)
[esp]
两者[esp+reg]
都以 3 个字节编码,[esp+8]
占用 4 个字节。因为循环在某个随机位置开始,所以额外的字节将指令(部分)推jne loop
送到下一个缓存行。
高速缓存行通常为 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
Run Code Online (Sandbox Code Playgroud)
此循环每次迭代应执行 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
Run Code Online (Sandbox Code Playgroud)
此循环每次迭代应花费 2 个周期。
通过inc eax
用 a替换add
以及inc [esp]
用不改变标志的指令替换 ,您可以允许 CPU 并行运行lea + mov
和add+jmp
指令。
这add
是在较新的 CPU 上可以更快,因为add
更改所有标志,而inc
仅更改标志的子集。
这可能会导致指令上的部分寄存器停顿jxx
,因为它必须等待对标志寄存器的部分写入得到解决。它mov [esp]
也更快,因为你没有做一个read-modify-write
循环,你只是在循环内写入内存。
通过展开循环可以获得进一步的收益,但收益会很小,因为这里的内存访问主导了运行时,而这从一开始就是一个愚蠢的循环。
总结一下:
inc
操作循环计数器,add
而是使用。 lea
当您对标志不感兴趣时,尝试使用添加。.align 16
。cmp
在循环内使用,inc/add
指令已经改变了标志。 归档时间: |
|
查看次数: |
411 次 |
最近记录: |