コンパイル時定数シフトの場合、かなり良い結果が得られます。そうでなければ本当に。
これは他に明らかな方法がないため、質問のr0
/r1
コードのSSE実装です。可変カウントシフトは、ベクタ要素内のビットシフトでのみ使用でき、レジスタ全体のバイトシフトでは使用できません。そのため、低い64ビットを64まで高値まで持ち運び、可変カウントシフトを使用して正しい位置に配置します。
// untested
#include <immintrin.h>
/* some compilers might choke on slli/srli with non-compile-time-constant args
* gcc generates the xmm, imm8 form with constants,
* and generates the xmm, xmm form with otherwise. (With movd to get the count in an xmm)
*/
// doesn't optimize for the special-case where count%8 = 0
// could maybe do that in gcc with if(__builtin_constant_p(count)) { if (!count%8) return ...; }
__m128i mm_bitshift_left(__m128i x, unsigned count)
{
__m128i carry = _mm_bslli_si128(x, 8); // old compilers only have the confusingly named _mm_slli_si128 synonym
if (count >= 64)
return _mm_slli_epi64(carry, count-64); // the non-carry part is all zero, so return early
// else
carry = _mm_srli_epi64(carry, 64-count); // After bslli shifted left by 64b
x = _mm_slli_epi64(x, count);
return _mm_or_si128(x, carry);
}
__m128i mm_bitshift_left_3(__m128i x) { // by a specific constant, to see inlined constant version
return mm_bitshift_left(x, 3);
}
// by a specific constant, to see inlined constant version
__m128i mm_bitshift_left_100(__m128i x) { return mm_bitshift_left(x, 100); }
これは、それが判明した以上に便利ではないと思いました。 _mm_slli_epi64
は、カウントがコンパイル時定数ではない(整数regからxmm regまでmovd
を生成する)場合でも、gcc/clang/iccで機能します。 _mm_sll_epi64 (__m128i a, __m128i count)
(i
の欠如に注意してください)がありますが、少なくとも最近では、i
の内在はpsllq
のいずれかの形式を生成できます。
コンパイル時定数カウントバージョンは、(AVXなし又は5)、compiling to 4 instructionsをかなり効率的である。
mm_bitshift_left_3(long long __vector(2)):
vpslldq xmm1, xmm0, 8
vpsrlq xmm1, xmm1, 61
vpsllq xmm0, xmm0, 3
vpor xmm0, xmm0, xmm1
ret
Performance:
これは3サイクルの待ち時間(vpslldqを有する(1) - > vpsrlq(1) - > vpor(1))をインテルSnB/IvB/Haswell上で実行し、スループットは2サイクルに1回に制限されます(ポート0のベクトルシフトユニットを飽和させる)。バイトシフトは、別のポートのシャッフルユニットで実行されます。イミディエイトカウントベクタシフトはすべてシングルuop命令ですので、これは他のコードと混在したときにパイプライン空間を占有するのは4つの融合ドメインuopsだけです。 (この機能の可変数のバージョンは、それが指示をカウントからの見た目より悪化しているように、可変数のベクトルシフトは、2 UOP、2サイクルのレイテンシがあります。)
または数> = 64のために:
mm_bitshift_left_100(long long __vector(2)):
vpslldq xmm0, xmm0, 8
vpsllq xmm0, xmm0, 36
ret
シフトカウントがではなく、コンパイル時定数がでない場合、キャリーを左右シフトするかどうかを調べるにはcount> 64で分岐する必要があります。シフトカウントは符号なし整数として解釈されるので、負のカウントは不可能です。
また、int
をカウントし、64カウントをベクトルレジスタに入れるには、余分な指示が必要です。これをベクトル比較とブレンド命令でブランチレスで行うことは可能かもしれませんが、ブランチはおそらく良い考えです。
GPレジスタの__uint128_t
の可変数バージョンはかなり見えます。 SSEバージョンよりも優れています。 Clang does a slightly better job than gcc, emitting fewer mov
instructionsですが、count> = 64の場合は2つのcmov
命令が引き続き使用されます。 `palignr`: 'M'は、8ビットの倍数であることを起こる、あなたがSSSE3を持っている場合(。x86の整数シフト命令ではなく、飽和の、数をマスクしているため)
__uint128_t leftshift_int128(__uint128_t x, unsigned count) {
return x << count; // undefined if count >= 128
}
、あなたは運がいい。そうでなければ、それは醜い速くなり、実際には、実際には、シフト、AND、シャッフル、ORを行う必要があります。 –
http://stackoverflow.com/questions/9980801/looking-for-sse-128-bit-shift-operation-for-non-immediate-shift-value –
ビットストリーム、または算術変数(ints、浮動小数点など)? – bazza