Как скопировать байты в регистр xmm0

У меня есть следующий код, который работает нормально, но кажется неэффективным, учитывая конечный результат, требующий только данные в xmm0

         mov rcx, 16                       ; get first word, up to 16 bytes
         mov rdi, CMD                      ; ...and put it in CMD
         mov rsi, CMD_BLOCK
 @@:     lodsb
         cmp al, 0x20
         je @f
         stosb
         loop @b

 @@:     mov rsi, CMD                      ;
         movdqa xmm0, [rsi]                ; mov cmd into xmm0

Я уверен, что используя SSE2, SSE4 и т. Д., Есть лучший способ, который не требует использования буфера CMD, но я изо всех сил пытаюсь понять, как это сделать.

1 ответ

Решение

Ваш код выглядит так, как будто он получает байты от CMD_BLOCK до первого 0x20, и я предполагаю, что нули выше этого.

Это даже не близко к наиболее эффективному способу создания цикла копирования по байтам. Никогда не используйте инструкцию LOOP, если вы специально не настраиваете одну из немногих архитектур, где она не медленная (например, AMD Bulldozer). Посмотрите материалы Agner Fog и другие ссылки из вики-тега x86. Или используйте SSE/AVX через встроенные функции C и позвольте компилятору сгенерировать фактический asm.

Но что более важно, вам даже не нужен цикл, если вы используете инструкции SSE.

Я предполагаю, что вы обнулили буфер CMD 16B перед началом копирования, иначе вы могли бы просто выполнить невыровненную загрузку и захватить любой байт мусора, который находится за пределами данных, которые вы хотите.

Все гораздо проще, если вы можете безопасно читать после окончания CMD_BLOCK, не вызывая segfault. Надеюсь, вы можете устроить так, чтобы это было безопасно. например, убедитесь, что это не в самом конце страницы, за которой следует не отображенная страница. Если нет, вам может потребоваться выполнить выровненную загрузку, а затем условно другую выровненную загрузку, если вы не получили конец данных.


SSE2 pcmpeqb, найдите первое совпадение и ноль байтов в этой позиции и выше

section .rodata

ALIGN 32              ; No cache-line splits when taking an unaligned 16B window on these 32 bytes
dd -1, -1, -1, -1
zeroing_mask:
dd  0,  0,  0,  0

ALIGN 16
end_pattern:  times 16   db 0x20    ; pre-broadcast the byte to compare against  (or generate it on the fly)

section .text

    ... as part of some function ...
    movdqu   xmm0, [CMD_BLOCK]       ; you don't have to waste instructions putting pointers in registers.
    movdqa   xmm1, [end_pattern]     ; or hoist this load out of a loop
    pcmpeqb  xmm1, xmm0

    pmovmskb eax, xmm1
    bsr      eax, eax                ; number of bytes of the vector to keep
    jz    @no_match                  ; bsr is weird when input is 0 :(
    neg      rax                     ; go back this far into the all-ones bytes
    movdqu   xmm1, [zeroing_mask + rax]   ; take a window of 16 bytes
    pand     xmm0, xmm1
@no_match:                          ; all bytes are valid, no masking needed
    ;; XMM0 holds bytes from [CMD_BLOCK], up to but not including the first 0x20.

На Intel Haswell это должно иметь задержку около 11 с от готовности входа PCMPEQB до готовности PAND.

Если бы вы могли использовать LZCNT вместо BSR, вы могли бы избежать ветвления. вы. Так как мы хотим получить 16 в случае отсутствия совпадения (поэтому neg eax дает -16, и мы загружаем вектор из всех единиц), 16-битный LZCNT справится с задачей. (lzcnt ax, ax работает, так как старшие байты RAX уже ноль от pmovmskb, Иначе xor ecx, ecx / lzcnt cx, ax)

Эта идея генерации маски с невыровненной загрузкой для получения окна из нескольких единиц и нулей совпадает с одним из моих ответов на Векторизацию с невыровненными буферами: использование VMASKMOVPS: создание маски из числа смещений? Или вообще не использовать этот insn.

Есть альтернативы загрузке маски из памяти. например, транслировать первый байт "все единицы" на все старшие байты вектора, удваивая длину маскированной области каждый раз, пока она не станет достаточно большой, чтобы охватить весь вектор, даже если байт 0xFF был первым байтом.

    movdqu   xmm0, [CMD_BLOCK]
    movdqa   xmm1, [end_pattern]
    pcmpeqb  xmm1, xmm0             ; 0 0 ... -1 ?? ?? ...

    movdqa   xmm2, xmm1
    pslldq   xmm2, 1
    por      xmm1, xmm2             ; 0 0 ... -1 -1 ?? ...

    movdqa   xmm2, xmm1
    pslldq   xmm2, 2
    por      xmm1, xmm2             ; 0 0 ... -1 -1 -1 -1 ?? ...

    pshufd   xmm2, xmm1, 0b10010000  ; [ a b c d ] -> [ a a b c ]
    por      xmm1, xmm2              ; 0 0 ... -1 -1 -1 -1 -1 -1 -1 -1 ?? ... (8-wide)

    pshufd   xmm2, xmm1, 0b01000000  ; [ abcd ] -> [ aaab ]
    por      xmm1, xmm2              ; 0 0 ... -1 (all the way to the end, no ?? elements left)
    ;; xmm1 = the same mask the other version loads with movdqu based on the index of the first match

    pandn    xmm1, xmm0              ; xmm1 = [CMD_BLOCK] with upper bytes zeroed


    ;; pshufd instead of copy + vector shift  works:
    ;; [ abcd  efgh  hijk  lmno ]
    ;; [ abcd  abcd  efgh  hijk ]  ; we're ORing together so it's ok that the first 4B are still there instead of zeroed.

SSE4.2 PCMPISTRM:

Если вы XOR используете свой терминатор так, чтобы 0x20 байтов стали 0x00 байтами, вы могли бы использовать строковые инструкции SSE4.2, поскольку они уже настроены для обработки строк неявной длины, где все байты, кроме 0x00, недопустимы. Посмотрите этот учебник / пример, потому что документация Intel просто документирует все во всех деталях, не сосредотачиваясь на важных вещах.

PCMPISTRM работает с задержкой 9 циклов на Skylake, задержкой 10c на Haswell и задержкой 7c на Nehalem. Так что речь идет о безубыточности для Haswell или фактически о потере, поскольку нам также нужен PXOR. Ищем 0x00 байтов и отмечаем элементы, которые жестко запрограммированы, поэтому нам нужно XOR, чтобы превратить 0x20 байтов в 0x00. Но это намного меньше мопов и меньше размера кода.

;; PCMPISTRM imm8:
;; imm8[1:0] = 00 = unsigned bytes
;; imm8[3:2] = 10 = equals each, vertical comparison.  (always not-equal since we're comparing the orig vector with one where we XORed the match byte)
;; imm8[5:4] = 11 = masked(-): inverted for valid bytes, but not for invalid  (TODO: get the logic on this and PAND vs. PANDN correct)
;; imm8[6] = 1 = output selection (byte mask, not bit mask)
;; imm8[7] = 0 (reserved.  Holy crap, this instruction has room to encode even more functionality??)

movdqu     xmm1, [CMD_BLOCK]

movdqa     xmm2, xmm1
pxor       xmm2, [end_pattern]       ; turn the stop-character into 0x00 so it looks like an implicit-length string
                                     ; also creating a vector where every byte is different from xmm1, so we get guaranteed results for the "valid" part of the vectors (unless the input string can contain 0x0 bytes)
pcmpistrm  xmm1, xmm2, 0b01111000    ; implicit destination operand: XMM0
pand       xmm0, xmm1

У меня, вероятно, нет точных аргументов для правильного pcmpistrm, но у меня нет времени, чтобы проверить или мысленно проверить это. Достаточно сказать, что я почти уверен, что можно создать маску, которая будет единственной перед первым нулевым байтом и со всеми последующими.

Другие вопросы по тегам