2

次のコードは問題なく動作しますが、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バッファを使用する必要のないより良い方法がありますが、その方法を見つけるのに苦労しています.

4

1 に答える 1

3

あなたのコードは、CMD_BLOCK から最初の 0x20 までのバイトを取得するように見えます。それより上のゼロが必要だと思います。

これは、一度に 1 バイトずつのコピー ループを作成する最も効率的な方法にはほど遠いものです。遅くない数少ないアーキテクチャの1 つ (AMD Bulldozer など) 用に特別にチューニングする場合を除き、LOOP 命令を使用しないでください。Agner Fog のもの、およびタグ wiki からの他のリンクを参照してください。または、C 組み込み関数を介して SSE/AVX を使用し、コンパイラに実際の asm を生成させます。

しかし、もっと重要なことは、SSE 命令を使用すれば、ループさえ必要ないということです。

コピーを開始する前に 16B CMD バッファーをゼロにしたと仮定しています。それ以外の場合は、アラインされていないロードを実行して、必要なデータ以外にあるガベージ バイトを取得することもできます。

segfault を発生させずに CMD_BLOCK の終わりを越えて安全に読み取ることができれば、事態ははるかに簡単になります。うまくいけば、それが安全になるように手配できます。たとえば、マップされていないページが続くページの最後に配置されていないことを確認します。そうでない場合は、整列されたロードを実行し、データの最後を取得できなかった場合は、条件付きで別の整列されたロードを実行する必要がある場合があります。


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 では、PAND の出力が準備されるまで、入力から PCMPEQB の準備が整うまでのレイテンシが約 11c になるはずです。

BSR の代わりにLZCNTを使用できれば、分岐を回避できます。あなた。不一致の場合は 16 が必要なので (つまり、neg eax は -16 を返し、すべて 1 のベクトルをロードします)、16 ビット LZCNT でうまくいきます。( lzcnt ax, axRAX の上位バイトは からすでにゼロであるため、機能しますpmovmskb。それ以外の場合はxor ecx, ecx/ lzcnt cx, ax)

いくつかのすべて 1 とすべて 0 のウィンドウを取得するための非整列ロードによるこのマスク生成のアイデアは、非整列バッファーを使用したベクトル化に関する私の回答の 1 つと同じです: VMASKMOVPS の使用: ミスアライメント カウントからマスクを生成しますか? または、その insn をまったく使用していません

メモリからマスクをロードする代わりの方法があります。たとえば、最初のすべて 1 のバイトをベクトルのすべての上位バイトにブロードキャストし、0xFF バイトが最初のバイトであっても、ベクトル全体をカバーするのに十分な大きさになるまで、マスクされた領域の長さを毎回 2 倍にします。

    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 :

0x20 バイトが 0x00 バイトになるようにターミネータと XOR する場合、0x00 を超えるすべてのバイトが無効な暗黙的な長さの文字列を処理するように既に設定されているため、SSE4.2 文字列命令を使用できる場合があります。このチュートリアル/例を参照してください。Intel のドキュメントは、最初に重要なことに焦点を当てずに、すべてを詳細に説明しているだけだからです。

PCMPISTRM は、Skylake で 9 サイクルのレイテンシ、Haswell で 10c のレイテンシ、Nehalem で 7c のレイテンシで実行されます。つまり、Haswell のレイテンシの損益分岐点、または PXOR も必要なため、実際には損失です。0x00 バイトの検索とそれを超える要素のマーキングはハードコーディングされているため、0x20 バイトを 0x00 に変換するには XOR が必要です。しかし、uops ははるかに少なく、コードサイズも小さくなっています。

;; 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 の正確な引数を正確に把握していない可能性がありますが、それをテストしたり、精神的に検証したりする時間はありません。最初のゼロ バイトの前にすべて 1 で、それ以降はすべて 1 のマスクを作成することが可能であると確信しています。

于 2016-09-18T02:25:09.467 に答える