周期が 2^128 の __m128i 変数の関数が必要です。(カウンターのように) 単調に増加する必要はありませんが、各値に 1 回アクセスします。
私が考えることができる最も単純な例は、実際には 128 ビットのカウンターですが、SSE で実装するのは難しいことがわかりました。よりシンプルで高速なソリューションはありますか?
周期が 2^128 の __m128i 変数の関数が必要です。(カウンターのように) 単調に増加する必要はありませんが、各値に 1 回アクセスします。
私が考えることができる最も単純な例は、実際には 128 ビットのカウンターですが、SSE で実装するのは難しいことがわかりました。よりシンプルで高速なソリューションはありますか?
これが単調なカウンターです。シンプルと言えるかどうかはわかりませんが。
と の両方が常にレジスタにあると仮定すると、これは 5 つの命令にコンパイルされるはずONE
です。ZERO
(VEX エンコーディングが使用されていない場合は 7 または 8)
inline __m128i nextc(__m128i x){
const __m128i ONE = _mm_setr_epi32(1,0,0,0);
const __m128i ZERO = _mm_setzero_si128();
x = _mm_add_epi64(x,ONE);
__m128i t = _mm_cmpeq_epi64(x,ZERO);
t = _mm_and_si128(t,ONE);
t = _mm_unpacklo_epi64(ZERO,t);
x = _mm_add_epi64(x,t);
return x;
}
テスト コード (MSVC):
int main() {
__m128i x = _mm_setr_epi32(0xfffffffa,0xffffffff,1,0);
int c = 0;
while (c++ < 10){
cout << x.m128i_u64[0] << " " << x.m128i_u64[1] << endl;
x = nextc(x);
}
return 0;
}
出力:
18446744073709551610 1
18446744073709551611 1
18446744073709551612 1
18446744073709551613 1
18446744073709551614 1
18446744073709551615 1
0 2
1 2
2 2
3 2
@Norbert Pによって提案されたわずかに優れたバージョン。元のソリューションよりも1命令節約できます。
inline __m128i nextc(__m128i x){
const __m128i ONE = _mm_setr_epi32(1,0,0,0);
const __m128i ZERO = _mm_setzero_si128();
x = _mm_add_epi64(x,ONE);
__m128i t = _mm_cmpeq_epi64(x,ZERO);
t = _mm_unpacklo_epi64(ZERO,t);
x = _mm_sub_epi64(x,t);
return x;
}
KISSの原則を決して忘れないでください。
これを貼り付ける(C標準でラップアラウンドするには符号なし整数が必要であるため、各値に1回だけアクセスします)。
__uint128_t inc(__uint128_t x) {
return x+1;
}
この歩留まりに(x64の場合):
addq $1, %rdi
adcq $0, %rsi
movq %rdi, %rax
movq %rsi, %rdx
ret
簡単/十分速い?addq
それをインライン化すると、おそらく/だけで解決できるでしょうadcq
(movq
sret
はx64 ABIで必要です。関数をインライン化する場合、それらは必須ではありません)
MSVCの厄介さに関するVooのコメントに対処するには、次を使用できます。
inline void inc(unsigned long long *x, unsigned long long *y) {
if (!++*x) ++*y; // yay for obfuscation!
}
近くにMSVCがインストールされていないため、テストできませんが、上記で投稿したものと同様の結果が得られるはずです。次に、本当に__m128iが必要な場合は、2つの半分をキャストできるはずです。