我需要一个__m128i变量的函数,其周期为2 ^ 128。它不需要单调增加(如计数器),而是访问每个值一次。
我能想到的最简单的例子实际上是一个128位计数器,但我发现在SSE中很难实现。有没有更简单/更快的解决方案?
答案 0 :(得分:5)
这是一个单调的计数器。我不确定你是否可以称之为简单。
假设ONE
和ZERO
都在寄存器中,那么这应该编译为5条指令。 (如果未使用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;
}
答案 1 :(得分:4)
永远不要忘记KISS原则。
粘贴(无符号整数需要用C标准包围,因此只访问每个值一次):
__uint128_t inc(__uint128_t x) {
return x+1;
}
进入this收益率(对于x64):
addq $1, %rdi
adcq $0, %rsi
movq %rdi, %rax
movq %rsi, %rdx
ret
容易/快吗?如果你内联这个内容,你可能只能使用addq
/ adcq
(movq
和ret
x64 ABI:{if}你内联函数,它们不是必需的)
要解决Voo关于MSVC嗜好的评论,您可以使用以下内容:
inline void inc(unsigned long long *x, unsigned long long *y) {
if (!++*x) ++*y; // yay for obfuscation!
}
我没有附近的MSVC安装,所以我无法测试它,但它应该产生类似于我上面发布的内容。然后,如果真的需要__m128i,那么你应该能够cast这两半。