10

私が取り組んでいる趣味のプロジェクトでは、x86 CPU で特定の 64 ビット整数演算をエミュレートする必要があり、高速である必要があります。

現在、MMX 命令を使用してこれを行っていますが、fp レジスタの状態を常にフラッシュする必要があるため (また、ほとんどの MMX 命令は符号付き整数を処理し、符号なしの動作が必要なため)、これを扱うのは本当に面倒です)。

したがって、SO に関する SSE/最適化の専門家が、SSE を使用してより良い実装を思い付くことができるかどうか疑問に思っています。

私が必要とする操作は、次の(非常に具体的な)操作です。

uint64_t X, Y;

X = 0;
X = 1;
X << 1;
X != Y;
X + 1;
X & 0x1 // get lsb
X | 0x1 // set lsb
X > Y;

具体的には、汎用の加算やシフトは必要ありません。たとえば、加算して左シフトするだけです。本当に、ここに示されている正確な操作だけです。

もちろん、x86 ではuint64_t2 つの 32 ビット スカラーを使用してエミュレートされますが、これは低速です (そして、私の場合は、ロード/ストアをアトミックにする必要があるため、機能しません。 2 つの別々のレジスタをロード/ストアする場合)。

したがって、SIMD ソリューションが必要です。これらの操作のいくつかは簡単で、SSE2 で既にサポートされています。その他 (!=および<) はもう少し作業が必要です。

提案?SSE と SSE2 は問題ありません。SSE3 を許可するにはある程度の説得が必要であり、SSE4 はおそらく問題外です (SSE4 をサポートする CPU はとにかく64 ビットで実行される可能性が高いため、これらの回避策は必要ありません)。

4

1 に答える 1

18

SSE2 は、いくつかの 64 ビット整数演算を直接サポートしています。

両方の要素を 0 に設定します。

__m128i z = _mm_setzero_si128();

両方の要素を 1 に設定します。

__m128i z = _mm_set1_epi64x(1);      // also works for variables.
__m128i z = _mm_set_epi64x(hi, lo);  // elements can be different

__m128i z = _mm_set_epi32(0,1,0,1);  // if any compilers refuse int64_t in 32-bit mode.  (None of the major ones do.)

下位 64 ビットを設定/ロードし、__m128i にゼロ拡張します

// supported even in 32-bit mode, and listed as an intrinsic for MOVQ
// so it should be atomic on aligned integers.
_mm_loadl_epi64((const __m128i*)p);     // movq or movsd 64-bit load

_mm_cvtsi64x_si128(a);      // only ICC, others refuse in 32-bit mode
_mm_loadl_epi64((const __m128i*)&a);  // portable for a value instead of pointer

に基づくもの_mm_set_epi32は、一部のコンパイラによって混乱にコンパイルされる可能性があるため、_mm_loadl_epi64MSVC と ICC、および gcc/clang で最善の策と思われ、実際には 32 ビット モードでのアトミック 64 ビット ロードの要件に対して安全である必要があります。 . Godbolt コンパイラ エクスプローラで参照してください

各 64 ビット整数を垂直方向に加算/減算します。

__m128i z = _mm_add_epi64(x,y)
__m128i z = _mm_sub_epi64(x,y)

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_sse2_integer_arithmetic.htm#intref_sse2_integer_arithmetic

左方移動:

__m128i z = _mm_slli_epi64(x,i)   // i must be an immediate

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_sse2_int_shift.htm

ビット演算子:

__m128i z = _mm_and_si128(x,y)
__m128i z = _mm_or_si128(x,y)

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_sse2_integer_logical.htm

SSE にはインクリメントがないため、 で定数を使用する必要があります1


pcmpeqqSSE4.1と SSE4.2までは 64 ビットのサポートがないため、比較は困難です。pcmpgtq

ここに平等のためのものがあります:

__m128i t = _mm_cmpeq_epi32(a,b);
__m128i z = _mm_and_si128(t,_mm_shuffle_epi32(t,177));

これにより、各 64 ビット要素が0xffffffffffff(別名-1)、それらが等しい場合) に設定されます。0またはとして使用する場合1int、 を使用して引き出して_mm_cvtsi32_si128()追加でき1ます。(ただしtotal -= cmp_result;、変換して追加する代わりに実行できる場合もあります。)

Less-Than: (完全にはテストされていません)

a = _mm_xor_si128(a,_mm_set1_epi32(0x80000000));
b = _mm_xor_si128(b,_mm_set1_epi32(0x80000000));
__m128i t = _mm_cmplt_epi32(a,b);
__m128i u = _mm_cmpgt_epi32(a,b);
__m128i z = _mm_or_si128(t,_mm_shuffle_epi32(t,177));
z = _mm_andnot_si128(_mm_shuffle_epi32(u,245),z);

0xffffffffffffの対応する要素aが より小さい場合、これにより、各 64 ビット要素が に設定されbます。


これは、bool を返す「equals」と「less-than」のバージョンです。下位 64 ビット整数の比較結果を返します。

inline bool equals(__m128i a,__m128i b){
    __m128i t = _mm_cmpeq_epi32(a,b);
    __m128i z = _mm_and_si128(t,_mm_shuffle_epi32(t,177));
    return _mm_cvtsi128_si32(z) & 1;
}
inline bool lessthan(__m128i a,__m128i b){
    a = _mm_xor_si128(a,_mm_set1_epi32(0x80000000));
    b = _mm_xor_si128(b,_mm_set1_epi32(0x80000000));
    __m128i t = _mm_cmplt_epi32(a,b);
    __m128i u = _mm_cmpgt_epi32(a,b);
    __m128i z = _mm_or_si128(t,_mm_shuffle_epi32(t,177));
    z = _mm_andnot_si128(_mm_shuffle_epi32(u,245),z);
    return _mm_cvtsi128_si32(z) & 1;
}
于 2012-04-19T09:43:04.360 に答える