获取最后一行分隔符

时间:2018-05-23 19:23:22

标签: assembly x86-64

我认为这是一项非常常见的任务,应该有一些快速简洁的解决方案。我有一个四字,我想得到最低字节位置,它等于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 +++

但程序看起来很难看,实际上我正在寻找一种尽可能快的方法。你能提供一些优化建议吗?

1 个答案:

答案 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

根据您对结果的处理方式,您可能会以不同的方式安排循环计数器。