10

次のコードを 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 比較の結果を含むレジスターがある場合 (そのスロットに0xfffforを置く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 レジスタが解放されます。

4

2 に答える 2

12

フロートを使用している場合は、通常、の代わりにMOVMSKPS(および対応する AVX 命令VMOVMSKPS)を使用しPMOVMSKBます。

それはさておき、はい、これはこれを行う標準的な方法の 1 つです。PTEST( ) を使用VPTESTして、SSE または AVX AND または ANDNOT の結果に基づいて条件フラグを直接更新することもできます。

于 2012-11-21T13:23:58.983 に答える
2

編集したバージョンに対処するには:

の結果で直接分岐する場合は、GP regPTESTを使用するよりもそれを使用し、その上で を実行して分岐命令のフラグを設定する方が高速です。AMD CPU では、ベクトル ドメインと整数ドメインの間でのデータの移動は非常に遅くなります (CPU モデルに応じて 5 ~ 10 サイクルのレイテンシ)。MOVMSKPSTEST

用に追加のレジスタが必要である限り、PTEST多くの場合必要ありません。通常の非ベクトルTEST命令と同様に、両方の引数に同じ値を使用できます。(テストfoo & fooはテストと同じfooです)。

あなたの場合、すべてのベクトル要素が設定されていることを確認する必要があります。比較を逆にして、結果を一緒に OR すると (つまり、!(x1 < a1[i]) || !(x2 < a2[i]) || ...をテストしている)、すべて 1 ではなく、すべて 0 をテストするために必要なベクトルが得られます。しかし、低要素の処理にはまだ問題があります。PTEST/のベクトル マスクが不要になるようにレジスタを保存する必要がある場合はVTESTPS、ベクトルを 4 バイト右シフトしPTESTてから、すべてゼロで分岐することができます。

AVX が導入されましVTESTPSた。これにより、可能性のある float -> int バイパス遅延が回避されると思います。ただし、int ドメイン命令を使用してテスト用の入力を生成した場合は、(V)PTEST. (あなたが組み込み関数を使用していたことは知っていますが、ニーモニックに比べて入力して見るのは面倒です。)

于 2015-06-09T02:27:18.833 に答える