各バイトは 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, 2
2 バイトのマスク データがレジスタの下位 2 バイトに配置されます。またはPUNPCKHQDQ
、またはPSHUFD
、別の reg の上位 64 を下位 64 に移動して、2 つの依存関係チェーンを取得することもできます。どのポートがどの命令 (シフトとシャッフル/抽出) で使用されているかを確認し、どのポートが と との競合が少ないかを確認する必要がPMOVSX
ありADDPD
ます。
punpck
pshufd
どちらも SnB で p1/p5 を使用しますpmovsx
。 addpd
p1 でのみ実行できます。 andpd
p5 でのみ実行できます。うーん、PAND
p0 (および 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 つの反復が実行可能です。
ああ、ADDPD
3 サイクルのレイテンシがあります。そのため、複数のアキュムレータをアンロールして使用し、ループを運ぶ依存関係チェーンがボトルネックになるのを回避する必要があります。おそらく 4 ずつ展開し、最後に 4 つのアキュムレータを合計します。組み込み関数を使用しても、ソースコードでこれを行う必要があります。これは、FP 数学の操作の順序が変更されるためです。コンパイラは、展開中にそれを実行しようとしない可能性があります。
したがって、4 倍に展開された各ループは、4 クロック サイクルに加えて、ループ オーバーヘッドに 1 uop かかります。小さなループ キャッシュはあるが uop キャッシュがない Nehalem では、アンロールは、デコーダのスループットを気にし始めなければならないことを意味するかもしれません。ただし、サンディブリッジ以前では、1 クロックあたり 1 回のロードがボトルネックになる可能性があります。
ANDPS
デコーダのスループットについては、おそらくの代わりに使用できますANDPD
。これにより、エンコードに必要なバイト数が 1 バイト少なくなります。それが役立つ場合はIDK。
これを 256bymm
レジスタに拡張するには、最も簡単な実装 ( の場合VPMOVSXBQ ymm
) に AVX2 が必要になります。2つを実行して、または何かとVPMOVSXBQ xmm
組み合わせることで、AVXのみでスピードアップが得られる場合があります。VINSERTF128