2016-09-18 21 views
2

1つのメモリ位置から別のメモリ位置に奇数番号のバイトをすべてコピーする必要があります。すなわち、第1、第3、第5などをコピーする。具体的には、2000文字/属性ワードを含むテキストエリア0xB8000からコピーしている。私は、属性のバイトをスキップして、文字で終了したい。次のコードは正常に動作します:2バイトごとにスキップできるSSE mov命令ですか?

 mov eax, ecx      ; eax = number of bytes (1 to 2000) 
     mov rsi, rdi      ; rsi = source 
     mov rdi, CMD_BLOCK     ; rdi = destination 
@@: movsb        ; copy 1 byte 
     inc rsi       ; skip the next source byte 
     dec eax 
     jnz @b  

数またはコピーする文字は、私は最近、SSE2、SSE3のSSE4.2で遊んで始めましたが、命令(複数可)を見つけることができない1から2000までの任意の場所でありますルーピングを減らすことができます。理想的には、一度に128ビットをロードした後、2バイトごとにスキップできる命令があれば、可能であろう250と言うために2000からループを切り捨てることが理想です。

答えて

2

私はこのような何かをするだろう、処理32の入力が ループ繰り返しあたり16個の出力バイトにバイト:

const __m128i vmask = _mm_set1_epi16(0x00ff); 

for (i = 0; i < n; i += 16) 
{ 
    __m128i v0 = _mm_loadu_si128(&a[2 * i]);  // load 2 x 16 input bytes (MOVDQU) 
    __m128i v1 = _mm_loadu_si128(&a[2 * i + 16]); 
    v0 = _mm_and_si128(v0, vmask);    // mask unwanted bytes  (PAND) 
    v1 = _mm_and_si128(v1, vmask); 
    __m128 v = _mm_packus_epi16(v0, v1);   // pack low bytes   (PACKUSWB) 
    _mm_storeu_si128(v, &b[i];     // store 16 output bytes (MOVDQU) 
} 

これは当然の組み込み関数とCです - あなたが本当にアセンブラでこれを行うにしたい場合、あなたは上の各組み込み関数を対応する命令に変換することができます。

+1

うん、まさに私が考えていたもの。結果ベクトルごとにシャッフルが1つしかないため、PSHUFBの任意の組み合わせよりも優れています。シャッフルのビット数はブール値のビットごとの演算数よりも低くなります。 –

+0

私はそれが梱包ステップを行うのに十分であるべきだと思います。 – fuz

+1

これはまさに私が探していたものです。とても有難い。 – poby

2

私はSIMD命令をまったく使用しません。ビデオメモリがキャッシュされておらず、バスがより広範なトランザクションをサポートしていない可能性があるので、パフォーマンスの64ビットの負荷を大幅に上回ることはできません。他のすべてはそれと並行して起こることができるそれは非効率的に見えるが、負荷(mov rax,[rdi])上の巨大なストールがありますので、

 lea rdi, [rdi + rcx * 2 - 8] 
loop: 
    mov rax, [rdi] 
    mov [CMD_BLOCK + rcx - 4], al 
    shr rax, 16 
    mov [CMD_BLOCK + rcx - 4 + 1], al 
    shr rax, 16 
    mov [CMD_BLOCK + rcx - 4 + 2], al 
    shr rax, 16 
    mov [CMD_BLOCK + rcx - 4 + 3], al 
    sub rdi, 8 
    sub rcx, 4 
    jnz loop 

私はこのようなものを使用すると思います。

またはC:

関係なく、あなたのコードのパフォーマンスは読み込みキャッシュされていないビデオメモリのコストによって支配されようとしている何の
void copy_text(void *dest, void *src, int len) { 
    unsigned long long *sp = src; 
    unsigned char *dp = dest; 
    int i; 

    for(i = 0; i < len; i += 4) { 
     unsigned long long a = *sp++; 
     *dp++ = (unsigned char) a; 
     a >>= 16; 
     *dp++ = (unsigned char) a; 
     a >>= 16; 
     *dp++ = (unsigned char) a; 
     a >>= 16; 
     *dp++ = (unsigned char) a; 
    } 
}  

。それは本当にあなたが最適化する必要がある唯一の部分です。

また、これらの読み取りを多く実行しているため、コードのパフォーマンスが実際に重要な場合は、通常のキャッシュメモリにテキストのコピーを保持できないかどうかを確認する必要があります。ビデオメモリは読み込み用に設計されていないので、実際には最後の手段にすべきです。 (または、このコードをLinuxカーネルなどで実行している場合は、通常のメモリにコピーがあるかどうかを確認してください)

+1

UCメモリでは、USWCからのようにNTロードで完全なキャッシュラインを取得できませんでしたが、1つのロードで16Bを得ることはできますか? Intelは、ビデオメモリからのMOVNTDQAロードの使用に関する記事を持っています:https://software.intel.com/en-us/articles/copying-accelerated-video-decode-frame-buffers (彼らはNTストアをWBメモリに使用し、NTロードからNTロードを分離するためにキャッシュされたバウンスバッファを使用する特別なトリックを使用し、部分ラインフィルを減らします)。 –

+0

@PeterCordesうーん...私はMOVNTDQA命令を知らなかった。プロセッサがメモリのUSWC属性を無視し、一度にキャッシュライン全体のロードを実行できるように見えます。実際にシステムRAMに入っているビデオメモリの場合は、勝利(DRAMへのバーストトランザクション)が必要ですが、PCI-Expressバスを介した読み取りで大きな改善が見込まれるかどうかはわかりません。私は、CPUによって開始される64ビット以上の読み込みが一般的にサポートされているかどうかはわかりません。 –

+1

MOVNTDQAは、メモリ順序付けセマンティクスBTWをオーバーライドしません。 [私の答えはこちら](http://stackoverflow.com/questions/32103968/non-temporal-loads-and-the-hardware-prefetcher-do-the-work-together)強く順序付けられた(WB)メモリでは、それは依然として強く順序付けられた負荷です。しかし、CPUはNTヒント(キャッシュ汚染を避ける)のような何かをすることができるかもしれないので、それでも役に立つかもしれません。私は大規模な* L3キャッシュタグを備えた最新のインテルでどのように実装されているのか、試そうとは思いませんでした。 –

2

実際にx86-64のVGAテキストモードビデオメモリでSIMDを使用していますかモード?これは面白いですが、実生活では実際にはそうであり、SIMDデータ操作のユースケースとして機能します。

しかし、ビデオメモリから実際に読み込んでいる場合は、キャッシュされていない読み込みが行われている可能性があります。これは悪いことであり、システムを再設計して行う必要はありません。 (提案のRossの答えを参照してください)

USWCビデオメモリでは、MOVNTDQAから大きなスピードアップを得ることができます。 Intel's articleを参照してください。NTについての私の答えは、here、特にthis oneを参照してください。ここでは、x86 ISAのマニュアルでNTのロードに関するセマンティクスをオーバーライドしないと説明しているので、弱い順序で使用しない限り、順序付けられたメモリ領域。


あなたが疑わしかったように、SIMD命令セットにはコピー命令がありません。あなたはロードとストアの間で自分でデータ処理をレジスタに登録しなければなりません。あなたのためにこれを行う単一のSSE/AVX命令さえありません。 (しかし、ARM NEONのunzip instructionは問題全体を解決します)。


あなたは(署名)の二つのベクトルをパックするSSE2 PACKUSWBを、使用すべきではuint8_tの一つのベクトルにダウンint16_t。各単語要素の上位バイトをゼロにした後、0..255に飽和してもデータはまったく変更されません。

ソースポインタを調整してキャッシュライン境界を越えるペナルティを最小限に抑え、いくつかのアドレッシングモードのトリックを使用してループ内に命令を保存する、実際の(テストされていない)ループ

アライメントされていないロードでは、Nehalem以降ではキャッシュラインの境界を横切るときに余分な遅延が発生します。これは、ビデオメモリからのNTロードを使用する場合に最も便利です。または、大量のコピーの最後にsrcの終わりを超えて読み込む場合には、これはおそらく便利です。

ストアの2倍の負荷がかかるため、ロード/ストアのスループットが問題となる場合は、整列したストアの代わりに最適化されたロードが問題になります。しかし、キャッシュのロード/ストアのスループットを飽和させるにはALUの作業が多すぎるため、のアラインされていないロード(ポールRのループのような)を単純にしておくと、ほとんどのCPUとユースケースでうまく動作するはずです。 AVXの非破壊第3オペランドの符号化に

mov  edx, CMD_BUFFER ; or RIP-relative LEA, or hopefully this isn't even static in the first place and this instruction is something else 

    ;; rdi = source ; yes this is "backwards", but if you already have the src pointer in rdi, don't waste instructions 
    ;; rcx = count 
    ;; rdx = dest 

    pcmpeqw xmm7, xmm7   ; all ones (0xFF repeating) 
    psrlw  xmm7, 8   ; 0x00FF repeating: mask for zeroing the high bytes 

    ;cmp  ecx, 16 
    ;jb  fallback_loop  ; just make CMD_BUFFER big enough that it's ok to copy 16 bytes when you only wanted 1. Assuming the src is also padded at the end so you can read without faulting. 

    ;; First potentially-unaligned 32B of source data 
    ;; After this, we only read 32B chunks of 32B-aligned source that contain at least one valid byte, and thus can't segfault at the end. 
    movdqu xmm0, [rdi]    ; only diff from loop body: addressing mode and unaligned loads 
    movdqu xmm1, [rdi + 16] 
    pand  xmm0, xmm7 
    pand  xmm1, xmm7 
    packuswb xmm0, xmm1 
    movdqu [rdx], xmm0 

    ;; advance pointers just to the next src alignment boundary. src may have different alignment than dst, so we can't just AND both of them 
    ;; We can only use aligned loads for the src if it was at least word-aligned on entry, but that should be safe to assume. 
    ;; There's probably a way to do this in fewer instructions. 
    mov  eax, edi 
    add  rdi, 32    ; advance 32B 
    and  rdi, -32    ; and round back to an alignment boundary 
    sub  eax, edi    ; how far rdi actually advanced 
    shr  eax, 1 
    add  rdx, rax    ; advance dst by half that. 

    ;; if rdi was aligned on entry, the it advances by 32 and rdx advances by 16. If it's guaranteed to always be aligned by 32, then simplify the code by removing this peeled unaligned iteration! 
    ;; if not, the first aligned loop iteration will overlap some of the unaligned loads/store, but that's fine. 

    ;; TODO: fold the above calculations into this other loop setup 

    lea  rax, [rdx + rdx] 
    sub  rdi, rax   ; source = [rdi + 2*rdx], so we can just increment our dst pointer. 

    lea  rax, [rdx + rcx] ; rax = end pointer. Assumes ecx was already zero-extended to 64-bit 



    ; jmp  .loop_entry  ; another way to check if we're already done 
    ; Without it, we don't check for loop exit until we've already copied 64B of input to 32B of output. 
    ; If small inputs are common, checking after the first unaligned vectors does make sense, unless leaving it out makes the branch more predictable. (All sizes up to 32B have identical branch-not-taken behaviour). 

ALIGN 16 
.pack_loop: 

    ; Use SSE4.1 movntdqa if reading from video RAM or other UCSW memory region 
    movdqa xmm0, [rdi + 2*rdx]   ; indexed addressing mode is ok: doesn't need to micro-fuse because loads are already a single uop 
    movdqa xmm1, [rdi + 2*rdx + 16] ; these could optionally be movntdqa loads, since we got any unaligned source data out of the way. 
    pand  xmm0, xmm7 
    pand  xmm1, xmm7 
    packuswb xmm0, xmm1 
    movdqa [rdx], xmm0  ; non-indexed addressing mode: can micro-fuse 
    add  rdx, 16 
.loop_entry: 
    cmp  rdx, rax 
    jb  .pack_loop   ; exactly 8 uops: should run at 1 iteration per 2 clocks 

    ;; copies up to 15 bytes beyond the requested amount, depending on source alignment. 

    ret 

、負荷がPANDs(vpand xmm0, xmm7, [rdi + 2*rdx])に折り畳むことができます。しかしindexed addressing modes can't micro-fuse on at least some SnB-family CPUsなので、宛先を基準にして送信元をアドレッシングするのではなく、add rdi, 32add rdx, 16の両方を展開することをお勧めします。

AVXは、2xload +と/ pack/storeの4つの融合ドメインuopsとループオーバーヘッドにループ本体をダウンさせます。アンローリングを行うことで、インテルハスウェルの理論的最大スループットは2ロード+ 1ストア/クロック(これは維持できませんが、ストアアドレスuopはp7を使用する代わりにp23サイクルを盗むことになります)すべてのL1キャッシュのヒット、96Bのピークスループット未満であると仮定すると(32バイトのベクターを使用して)〜84Bロードされ、クロックごとに保存されたようなものの持続可能なスループット数を-world。)


あなたはまた、使用することができます低位64ビットにパックされたベクトルの偶数バイトを得るために、2バイトシャッフル(SSSE3 PSHUFB)を使用します。 (その後、128ビットのロードごとに1つの64ビットMOVQストアを実行するか、2つ下半分をPUNPCKLQDQと組み合わせる)。しかし、(ソースデータの128ビットベクトルごとに)2シャッフル+ 2ストア、または3シャッフル+ 1ストアであるため、これはうんざりです。異なるシャッフルマスクを使用して、より安価にマージすることができます。偶数バイトを1つのベクトルの下半分と別のベクトルの上半分にシャッフルします。 PSHUFBは空きバイトもゼロにすることができるので、POR(少し高価なPBLENDWまたはAVX2 VPBLENDDの代わりに)と組み合わせることができます。これは2シャッフル+ 1ブール+ 1ストアですが、シャッフルにボトルネックが残ります。

PACKUSWBの方法は2ブール演算+ 1シャッフル+ 1ストアです(PANDはシャットルの場合は1クロック/ 3など、より多くの実行ポートで実行できるため、ボトルネックはありません)。(Skylake-avx512 but not on KNLで利用可能)


AVX512BWは切り捨て代わりの飽和と
VPMOVWB ymm1/m256 {k1}{z}, zmm2__m256i _mm512_cvtepi16_epi8 (__m512i a))、パックを提供します。 SSEパック命令とは異なり、入力は1つしかなく、結果はより狭くなります(これはメモリの宛先となります)。 (vpmovswbvpmovuswbは類似しており、符号付きまたは符号なしの彩度でパックされています。pmovzxと同じサイズのコンボが使用可能です(例:vpmovqb xmm1/m64 {k1}{z}, zmm2)。

メモリデスティネーション機能はC/C++の組み込み関数でも公開されているため、Cでマスクされたストアを便利にコーディングすることができます(これはpmovzx where it's inconvenient to use intrinsics and get the compiler to emit a pmovzx loadからの素晴らしい変更です)。 (インテルCannonlakeで予想)

AVX512VBMIつの入力ベクトルから偶数バイトを受け取り、単一の結果ベクトルを生成するシャッフルマスク所与一VPERMT2Bと、ワン512Bの出力に2つの入力を行うことができます。

VPERM2TBがVPMOVWBよりも遅い場合は、一度に1つのベクトルにVPMOVWBを使用するのが最適です。それらが同じスループット/レイテンシ/ウオップカウントを持っていても、ゲインは非常に小さいので、AVX512BWの代わりに別のバージョンと検出AVX512VBMIを作る価値はありません。 (CPUがAVX512BWを持たなくてもAVX512VBMIを持っている可能性は低いですが)。

+0

趣味のキーボードハンドラのために書いていますので、ミッションクリティカルではありませんが、私は習得したいと思っています。ビデオメモリへの読み書きはどれくらい遅いですか?ラムより数百倍遅いか、2〜3倍遅くなりますか? – poby

+1

@poby:Cool。私は非効率的なコードも好きではありません。しかし、ループのパフォーマンスは重要ではないので、この場合の全体的なパフォーマンスにとって最良のことは、おそらく、命令キャッシュの追い出しを減らすために、コードサイズを小さく保つことです。だから、おそらくちょうどあなたが最後を過ぎて読むことを避ける必要がない場合は、常にアライメントされていないロード/ストアを使用してください。あるいは、ロスが提案したようにスカラー化することさえできます。 (おそらくより広い店のためのレジスタにいくつかのバイトを組み合わせるでしょう。) –

+1

@poby:re:ビデオメモリ。 IDKですが、それがビデオカードの場合は、数千倍または数千倍のレイテンシを必要とします。なぜなら、L1キャッシュでヒットするだけではないからです。私はあなたがワイド​​読み取りを行う場合、特にMOVNTDQAを使って完全なキャッシュライン転送を取得する場合、スループットがOK *になると思います。メインメモリ(つまり、CPUに物理的に取り付けられたメモリを使用する統合グラフィックス)にある場合は、おそらくまだキャッシュ不可とマークされています。通常のWriteBackメモリ領域より数百倍のレイテンシがありますが、SSE4.1 NTが読み込まれている場合は通常のメモリと同じスループットになります。 –

関連する問題