我认为这是一项非常常见的任务,应该有一些快速简洁的解决方案。我有一个四字,我想得到最低字节位置,它等于0x0A(Linux中的换行符)。我写了以下简单的程序:
SYS_exit equ 0x3C
section .text
global _start
_start:
mov rax, 0x0A
mov rbx, [dt]
mov rcx, 0x07
loop:
mov r13, rbx
and r13, 0xFF
cmp r13, 0x0A
jz ok
shr rbx, 8
dec rcx
jnz loop
jmp fail
ok:
mov rax, SYS_exit
mov rdi, 8
sub rdi, rcx
syscall
fail:
mov rax, SYS_exit
mov rdi, -1
syscall
section .data
dt: dq 0xAB97450A8733AA1F
它的效果非常好。 strace ./bin
打印
execve("./bin", ["./bin"], [/* 69 vars */]) = 0
exit(5) = ?
+++ exited with 5 +++
但程序看起来很难看,实际上我正在寻找一种尽可能快的方法。你能提供一些优化建议吗?
答案 0 :(得分:3)
但程序看起来很难看
恭喜注意到:P
我正在寻找一种尽可能快的方法
SSE2是x86-64的基线,因此您应该使用它。您可以在几条指令中执行此操作,使用pcmpeqb / pmovmskb获取字节比较结果的位图,然后使用像bsr
这样的位扫描指令(扫描反向为您提供最高设置位的索引)。
default rel ; don't forget this: RIP-relative addressing is best for loading/storing global data
_start:
movq xmm0, [dt] ; movq xmm0, rdx or whatever works too.
pcmpeqb xmm0, [newline_mask] ; -1 for match, 0 for no-match
pmovmskb edi, xmm0
bsr edi, edi ; index of highest set bit
mov eax, SYS_exit
jz .not_found ; BSR sets ZF if the *input* was zero
; [dt+rdi] == 0xA
syscall ; exit(0..7)
.not_found:
mov edi, -1 ; exit only cares about the low byte of its arg; a 64-bit -1 is pointless.
syscall
section .rodata
align 16
newline_mask: times 16 db 0x0a
section .data
dt: dq 0xAB97450A8733AA1F
显然,在一个循环中,您将newline_mask
保留在一个寄存器中(然后您可以使用AVX vbroadcastss
或SSE3 movddup
进行广播加载,而不需要内存中的整个16字节常量。)
当然,您可以使用movdqu
加载一次执行16个字节,或者使用AVX2一次执行32个字节。 如果您有一个大缓冲区,那么您基本上实现了向后memcmp
,并且应该查看优化的库实现。它们可能会将pcmpeqb
结果合并为一个整个缓存行使用por
,因此他们将pmovmskb
工作的3/4保存到最后,直到他们整理出缓存行的哪一部分有效。
如果您关心AMD CPU(bsr
速度很慢),可以在使用test edi,edi
之前使用jz
/ tzcnt
分别测试输入= 0。 (tzcnt(x)
为您提供31-bsr(x)
,如果输入为全零,则为32。)如果您可以依赖BMI2可用...
如果你想用标量循环来做,你可以在寄存器的低字节上使用字节比较,而不是复制和屏蔽该值。
; we test byte 7 first, so start the counter there.
mov edi, 7 ; no idea why you were using a 64-bit counter register
; loop body runs with edi=7..0
.loop: ; do{
rol rbx, 8 ; high byte becomes low
cmp bl, 0xa ; check the low byte
je .found
dec edi
jge .loop ; } while(--edi>=0) signed compare
; not found falls through with edi=-1
.found:
mov eax, SYS_exit
syscall ; exit(7..0) for found, or exit(-1) for not-found
根据您对结果的处理方式,您可能会以不同的方式安排循环计数器。