我需要一个周期为 2^128 的 __m128i 变量的函数。它不需要单调增加(如计数器),而是访问每个值一次。
我能想到的最简单的例子实际上是一个 128 位的计数器,但我发现这很难在 SSE 中实现。有没有更简单/更快的解决方案?
我需要一个周期为 2^128 的 __m128i 变量的函数。它不需要单调增加(如计数器),而是访问每个值一次。
我能想到的最简单的例子实际上是一个 128 位的计数器,但我发现这很难在 SSE 中实现。有没有更简单/更快的解决方案?
这是一个单调的计数器。我不确定你是否可以称之为简单。
假设两者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;
}
永远不要忘记 KISS 原则。
粘贴这个(无符号整数需要被 C 标准环绕,因此每个值只访问一次):
__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
s 和ret
x64 ABI 需要:如果你内联函数,它们不是必需的)
要解决 Voo 关于 MSVC 的缺点的评论,您可以使用以下内容:
inline void inc(unsigned long long *x, unsigned long long *y) {
if (!++*x) ++*y; // yay for obfuscation!
}
我附近没有安装 MSVC,所以我无法对其进行测试,但它应该会产生类似于我上面发布的内容。然后,如果你真的需要一个 __m128i,你应该可以投两半。