Получить разделитель последней строки - PullRequest
0 голосов
/ 23 мая 2018

Я думаю, что это довольно распространенная задача, и должно быть какое-то быстрое и краткое решение.У меня есть quadword, и я хочу получить младшую байтовую позицию, равную 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 голосов
/ 23 мая 2018

Но программа выглядит некрасиво

Поздравляю с замечанием: 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-байтовая константа в памяти).

И, конечно, вы можете сделать это для 16 байт за раз с нагрузкой movdqu или 32 байт за раз с AVX2. Если у вас большой буфер, вы в основном реализуете memcmp в обратном направлении и должны смотреть на оптимизированные реализации библиотек. Они могут объединять pcmpeqb результаты для всей строки кэша с por, поэтому онисохраните 3/4 из pmovmskb работы до конца, когда они выяснят, какая часть строки кэша имела удар.

Если вы заботитесь о процессорах AMD (где bsr медленно), возможно, отдельнопроверить на вход = 0 с помощью test edi,edi / jz перед использованием tzcnt.(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

В зависимости от того, что вы делаете с результатом, вы можете расположить счетчик цикла по-разному.

...