7

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

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

4

2 回答 2

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;
}
于 2012-02-19T15:01:29.950 回答
4

永远不要忘记 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/ adcqmovqs 和retx64 ABI 需要:如果你内联函数,它们不是必需的)


要解决 Voo 关于 MSVC 的缺点的评论,您可以使用以下内容:

inline void inc(unsigned long long *x, unsigned long long *y) {
  if (!++*x) ++*y; // yay for obfuscation!
}

我附近没有安装 MSVC,所以我无法对其进行测试,但它应该会产生类似于我上面发布的内容。然后,如果你真的需要一个 __m128i,你应该可以两半。

于 2012-02-19T18:37:16.573 回答