128位SSE计数器?

时间:2012-02-19 12:03:20

标签: optimization sse intrinsics avx

我需要一个__m128i变量的函数,其周期为2 ^ 128。它不需要单调增加(如计数器),而是访问每个值一次。

我能想到的最简单的例子实际上是一个128位计数器,但我发现在SSE中很难实现。有没有更简单/更快的解决方案?

2 个答案:

答案 0 :(得分:5)

这是一个单调的计数器。我不确定你是否可以称之为简单。

假设ONEZERO都在寄存器中,那么这应该编译为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 / adcqmovqret 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这两半。