AVX/SSE版xorshift128 +

ele*_*ora 10 c performance sse avx

我正在努力制作最快的高质量RNG.阅读http://xorshift.di.unimi.it/,xorshift128 +似乎是一个不错的选择.C代码是

#include <stdint.h>
uint64_t s[ 2 ];

uint64_t next(void) { 
    uint64_t s1 = s[ 0 ];
    const uint64_t s0 = s[ 1 ];
    s[ 0 ] = s0;
    s1 ^= s1 << 23; // a
    return ( s[ 1 ] = ( s1 ^ s0 ^ ( s1 >> 17 ) ^ ( s0 >> 26 ) ) ) + s0; // b, c
}
Run Code Online (Sandbox Code Playgroud)

我不是SSE/AVX专家,但我的CPU支持SSE4.1/SSE4.2/AVX/F16C/FMA3/XOP指令.你怎么能用这些来加速这段代码(假设你想要制作数十亿这样的随机数),那么在实践中这个加速的预期限制是多少?

usr*_*usr 7

XorShift确实是一个不错的选择.它是如此的好,如此之快,需要如此少的状态,我很惊讶地看到这么少的采用.它应该是所有平台上的标准生成器.我已经在8年前自己实现了它,即使这样它也可以产生800MB/s的随机字节.

您不能使用向量指令来加速生成单个随机数.这几条指令中的指令级并行性太少.

但是您可以轻松地加速生成N个数字,其中N是目标指令集的向量大小.只需并行运行N个发电机.保持N个生成器的状态并同时生成N个数字.

如果客户端代码一次需要一个数字,则可以保留N(或更多)数字的缓冲区.如果缓冲区为空,则使用向量指令填充它.如果缓冲区不为空,则只返回下一个数字.

  • 您可以使用SSE并行运行两个这样的生成器 - 当然,您需要对它们进行不同的种子生成,否则它们只会为每个生成相同的值.使用`_mm_srli_epi64`作为右移,`_mm_xor_si128`作为XOR,使用`_mm_add_epi64`作为加法. (4认同)
  • 不幸的是,你没有AVX中的等效操作 - 你需要AVX2,它可以在Haswell和更高版本上使用 - 这将使你能够并行运行4 x 64位RNG而不是2. (2认同)

o90*_*000 5

对于其他可能会遇到这个问题的人,我认为这个C++代码使用AVX2正确地实现了4个并行运行的xorshift128plus生成器:

__m256i xorshift128plus_avx2(__m256i &state0, __m256i &state1)
{
    __m256i s1 = state0;
    const __m256i s0 = state1;
    state0 = s0;
    s1 = _mm256_xor_si256(s1, _mm256_slli_epi64(s1, 23));
    state1 = _mm256_xor_si256(_mm256_xor_si256(_mm256_xor_si256(s1, s0),
                                               _mm256_srli_epi64(s1, 18)),
                              _mm256_srli_epi64(s0, 5));
    return _mm256_add_epi64(state1, s0);
}
Run Code Online (Sandbox Code Playgroud)

我使用的标量实现是:

u64 xorshift128plus(u64 &state0, u64 &state1)
{
    u64 s1 = state0;
    const u64 s0 = state1;
    state0 = s0;
    s1 ^= s1 << 23;                              // a
    state1 = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5); // b, c
    return state1 + s0;
}
Run Code Online (Sandbox Code Playgroud)

这与xorshiftplus文章中的相同.请注意,原始问题的右移常数与文中的右移常数不一致.

  • 如果编译器不知道`_mm256_xor`是关联的,你可以使用不同的操作顺序来获得更多的指令级并行性.`s1 ^ s0`可以与班次并行运行.哎呀NVM,我误读了,s1 ^ s0实际上是在最里面的嵌套.我发现内部函数的大嵌套在单个表达式中非常难以理解,并且发现使用描述性名称命名临时变量以获得更多可读性更好.所以[gcc确实在这里做了很好的代码](http://goo.gl/PJJ​​RzQ). (3认同)