次のコードを SSE/AVX に変換しようとしています。
float x1, x2, x3;
float a1[], a2[], a3[], b1[], b2[], b3[];
for (i=0; i < N; i++)
{
if (x1 > a1[i] && x2 > a2[i] && x3 > a3[i] && x1 < b1[i] && x2 < b2[i] && x3 < b3[i])
{
// do something with i
}
}
ここで、N は小さな定数です。たとえば、8 とします。if(...) ステートメントは、ほとんどの場合、false と評価されます。
最初の試み:
__m128 x; // x1, x2, x3, 0
__m128 a[N]; // packed a1[i], a2[i], a3[i], 0
__m128 b[N]; // packed b1[i], b2[i], b3[i], 0
for (int i = 0; i < N; i++)
{
__m128 gt_mask = _mm_cmpgt_ps(x, a[i]);
__m128 lt_mask = _mm_cmplt_ps(x, b[i]);
__m128 mask = _mm_and_ps(gt_mask, lt_mask);
if (_mm_movemask_epi8 (_mm_castps_si128(mask)) == 0xfff0)
{
// do something with i
}
}
これは機能し、かなり高速です。問題は、これを行うためのより効率的な方法があるかどうかです。特に、float での SSE または AVX 比較の結果を含むレジスターがある場合 (そのスロットに0xffff
orを置く0x0000
)、一般に、すべての比較の結果を (たとえば) and ed または or ed にするにはどうすればよいですか? ? PMOVMSKB
(または対応する_mm_movemask
組み込み)はこれを行う標準的な方法ですか?
また、上記のコードで SSE の代わりに AVX 256 ビット レジスタを使用するにはどうすればよいですか?
編集:
以下に示すように、VPTEST (_mm_test* 組み込み関数から) を使用してバージョンをテストし、ベンチマークしました。
__m128 x; // x1, x2, x3, 0
__m128 a[N]; // packed a1[i], a2[i], a3[i], 0
__m128 b[N]; // packed b1[i], b2[i], b3[i], 0
__m128i ref_mask = _mm_set_epi32(0xffff, 0xffff, 0xffff, 0x0000);
for (int i = 0; i < N; i++)
{
__m128 gt_mask = _mm_cmpgt_ps(x, a[i]);
__m128 lt_mask = _mm_cmplt_ps(x, b[i]);
__m128 mask = _mm_and_ps(gt_mask, lt_mask);
if (_mm_testc_si128(_mm_castps_si128(mask), ref_mask))
{
// do stuff with i
}
}
これも機能し、高速です。これをベンチマークすると (Intel i7-2630QM、Windows 7、cygwin 1.7、cygwin gcc 4.5.3 または mingw x86_64 gcc 4.5.3、N=8)、64 ビットで上記のコードと同じ速度 (0.1% 未満) であることが示されます。 . 内側のループのどちらのバージョンも、すべてがキャッシュにあり、比較が常に false を返すデータに対して、平均約 6.8 クロックで実行されます。
興味深いことに、32 ビットでは、_mm_test バージョンの実行速度が約 10% 遅くなります。ループのアンロール後にコンパイラがマスクをスピルし、それらを再読み取りする必要があることが判明しました。これはおそらく不要であり、手作業でコーディングされたアセンブリでは回避できます。
どの方法を選択しますか? VPTEST
よりも優先するやむを得ない理由はないようですVMOVMSKPS
。実際には、優先するわずかな理由がありますVMOVMSKPS
。つまり、そうでなければマスクによって使用される xmm レジスタが解放されます。