7

Intel コンパイラの組み込み関数を使用して、128 ビットのレジスタが与えられ、8 つの 16 ビット要素がパックされている場合、レジスタ内から任意の要素に (安価に) アクセスして、後で_mm_cvtepi8_epi64(下位にパックされた 2 つの 8 ビット要素を符号拡張する) を使用するにはどうすればよいですか?レジスタの 16 ビットから 2 つの 64 ビット要素まで)?


私が尋ねる理由を説明します:

  1. 入力: k バイトのメモリ内バッファ。それぞれ 0x0 または 0xff です。
  2. 必要な出力: 入力の連続する 2 バイトごとに、2 つのクワッド ワード (64 ビット) をそれぞれ0x0および0xffff ffff ffff ffffでパッキングするレジスタ。
  3. 最終的な目標: 入力バッファーのエントリに従ってマスクされた、k 個の double のバッファーを合計します。

注: 入力バッファの値0x0と値0xffは、合計前のマスキングの効果が残っている限り、最も役立つものに変更できます。

私の質問から明らかなように、私の現在の計画は次のとおりで、入力バッファ全体にストリーミングします。

  1. 入力マスク バッファーを 8 ビットから 64 ビットに拡張します。
  2. 拡張マスクを使用して倍精度バッファーをマスクします。
  3. マスクされた double を合計します。

ありがとう、アサフ

4

3 に答える 3

3

各バイトは double 全体のマスクであるため、まさにPMOVSXBQ必要なことを行います: ポインターから 2 バイトをロードm16し、それらを xmm レジスターの 2 つの 64 ビット (qword) 半分に符号拡張します。

# UNTESTED CODE
# (loop setup stuff)
# RSI: double pointer
# RDI: mask pointer
# RCX: loop conter = mask byte-count
    add   rdi, rcx
    lea   rsi, [rsi + rcx*8]  ; sizeof(double) = 8
    neg   rcx  ; point to the end and count up

    XORPS xmm0, xmm0  ; clear accumulator
      ; for real use: use multiple accumulators
      ; to hide ADDPD latency

ALIGN 16
.loop:
    PMOVSXBQ XMM1, [RDI + RCX]
    ANDPD    XMM1, [RSI + RCX * 8]
    ADDPD    XMM0, XMM1
    add      RCX, 2      ; 2 bytes / doubles per iter
    jl       .loop

    MOVHLPS  XMM1, XMM0    ; combine the two parallel sums
    ADDPD    XMM0, XMM1 
    ret

実際の使用では、複数のアキュムレータを使用してください。インデックス付きアドレッシング モードに関するマイクロ フュージョンおよびアドレッシング モードも参照してください。

これを組み込み関数で書くのは簡単なはずです。他の人が指摘したように、逆参照されたポインターを組み込み関数への引数として使用するだけです。


質問の他の部分に答えるには、データをシフトして並べ替える方法についてPMOVSX

Sandybridge 以降では、RAM から PMOVSXBQ を使用するのがおそらく適切です。サイクルごとに 2 つのロードを処理できない以前の CPU では、一度に 16B のマスク データをロードし、一度に 2 バイトずつシフトすると、PSRLDQ xmm1, 22 バイトのマスク データがレジスタの下位 2 バイトに配置されます。またはPUNPCKHQDQ、またはPSHUFD、別の reg の上位 64 を下位 64 に移動して、2 つの依存関係チェーンを取得することもできます。どのポートがどの命令 (シフトとシャッフル/抽出) で使用されているかを確認し、どのポートが と との競合が少ないかを確認する必要がPMOVSXありADDPDます。

punpckpshufdどちらも SnB で p1/p5 を使用しますpmovsxaddpdp1 でのみ実行できます。 andpdp5 でのみ実行できます。うーん、PANDp0 (および p1/p5) で実行できるため、より良いかもしれません。そうしないと、ループ内で実行ポート 0 が使用されることはありません。integer ドメインから fp ドメインにデータを移動する際にレイテンシ ペナルティがある場合PMOVSX、 int ドメインでマスク データが取得されるため、 を使用すると避けられません。より多くのアキュムレータを使用して、ループを最長の依存関係チェーンよりも長くすることをお勧めします。ただし、ループ バッファーに収まるように 28uops 以下に保ち、サイクルごとに 4 つの uops を発行できるようにします。

さらに、全体の最適化について: nehalem 以降ではループ バッファーに収まるため、ループのアライメントは実際には必要ありません。

Haswell 以前の Intel CPU には、1 サイクルで 4 つの (融合された) uops すべてを処理するのに十分な実行ユニットがないため、ループを 2 または 4 ずつ展開する必要があります。(3 つのベクトルと 1 つの融合add/ jl。2 つのロードは、それらが属するベクター uop と融合します。) Sandybridge 以降では、サイクルごとに両方のロードを実行できるため、ループ オーバーヘッドを除いて、サイクルごとに 1 つの反復が実行可能です。

ああ、ADDPD3 サイクルのレイテンシがあります。そのため、複数のアキュムレータをアンロールして使用し、ループを運ぶ依存関係チェーンがボトルネックになるのを回避する必要があります。おそらく 4 ずつ展開し、最後に 4 つのアキュムレータを合計します。組み込み関数を使用しても、ソースコードでこれを行う必要があります。これは、FP 数学の操作の順序が変更されるためです。コンパイラは、展開中にそれを実行しようとしない可能性があります。

したがって、4 倍に展開された各ループは、4 クロック サイクルに加えて、ループ オーバーヘッドに 1 uop かかります。小さなループ キャッシュはあるが uop キャッシュがない Nehalem では、アンロールは、デコーダのスループットを気にし始めなければならないことを意味するかもしれません。ただし、サンディブリッジ以前では、1 クロックあたり 1 回のロードがボトルネックになる可能性があります。

ANDPSデコーダのスループットについては、おそらくの代わりに使用できますANDPD。これにより、エンコードに必要なバイト数が 1 バイト少なくなります。それが役立つ場合はIDK。


これを 256bymmレジスタに拡張するには、最も簡単な実装 ( の場合VPMOVSXBQ ymm) に AVX2 が必要になります。2つを実行して、または何かとVPMOVSXBQ xmm組み合わせることで、AVXのみでスピードアップが得られる場合があります。VINSERTF128

于 2015-06-04T22:21:30.113 に答える
3

むしろ質問自体に接して、コメントセクション自体が小さすぎてこれを保持できないため、コメントに関するいくつかの情報をさらに記入します(原文のまま!):

少なくとも gcc は次のコードを処理できます。

#include <smmintrin.h>

extern int fumble(__m128i x);

int main(int argc, char **argv)
{
    __m128i foo;
    __m128i* bar = (__m128i*)argv;

    foo = _mm_cvtepi8_epi64(*bar);

    return fumble(foo);
}

これを次のアセンブリに変換します。

セクション .text.startup の分解:

0000000000000000 :
   0: 66 0f 38 22 06 pmovsxbq (%rsi),%xmm0
   5: e9 XX XX XX XX jmpq .....

これは、組み込み関数がメモリ引数の形式である必要がないことを意味します。コンパイラは mem 引数の逆参照を透過的に処理し、可能であれば対応する mem-operand 命令を使用します。ICCも同様です。MSVCも同様にテストできるかどうかをテストするためのWindowsマシン/ Visual C ++はありませんが、期待しています。

于 2012-04-04T23:31:25.067 に答える
2

_mm_extract_epi16 (PEXTRW)_mm_insert_epi16 (PINSRW)を見ましたか?

于 2012-04-01T11:37:51.770 に答える