あなたのコードは、CMD_BLOCK から最初の 0x20 までのバイトを取得するように見えます。それより上のゼロが必要だと思います。
これは、一度に 1 バイトずつのコピー ループを作成する最も効率的な方法にはほど遠いものです。遅くない数少ないアーキテクチャの1 つ (AMD Bulldozer など) 用に特別にチューニングする場合を除き、LOOP 命令を使用しないでください。Agner Fog のもの、およびx86タグ 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, ax
RAX の上位バイトは からすでにゼロであるため、機能します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.
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 のマスクを作成することが可能であると確信しています。