我的向量化 xorshift+ 不是很随机
My vectorized xorshift+ is not very random
我有以下代码(the xorshift128+
code from Wikipedia 修改为使用矢量类型):
#include <immintrin.h>
#include <climits>
__v8si rand_si() {
static auto s0 = __v4du{4, 8, 15, 16},
s1 = __v4du{23, 34, 42, 69};
auto x = s0, y = s1;
s0 = y;
x ^= x << 23;
s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
return (__v8si)(s1 + y);
}
#include <iostream>
#include <iomanip>
void foo() {
//Shuffle a bit. The result is much worse without this.
rand_si(); rand_si(); rand_si(); rand_si();
auto val = rand_si();
for (auto it = reinterpret_cast<int*>(&val);
it != reinterpret_cast<int*>(&val + 1);
++it)
std::cout << std::hex << std::setfill('0') << std::setw(8) << *it << ' ';
std::cout << '\n';
}
输出
09e2a657 000b8020 1504cc3b 00110040 1360ff2b 00150078 2a9998b7 00228080
每隔一个数字都非常小,none设置了前导位。另一方面,使用 xorshift* 代替:
__v8si rand_si() {
static auto x = __v4du{4, 8, 15, 16};
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
return x * (__v4du)_mm256_set1_epi64x(0x2545F4914F6CDD1D);
}
我得到了更好的输出
0889632e a938b990 1e8b2f79 832e26bd 11280868 2a22d676 275ca4b8 10954ef9
但是根据维基百科,xorshift+ 是一个很好的 PRNG,并且产生比 xorshift* 更好的伪随机性。那么,是我的 RNG 代码有错误,还是我用错了?
我认为您不应该通过查看随机生成器生成的 8 个数字来判断它。此外,生成器通常需要良好的播种(您的播种可以被认为是糟糕的 - 您的种子几乎所有位都以零开始。仅调用 rand_si()
几次不足以使位达到 "spread")。
所以我建议您使用适当的种子(例如,一个简单的解决方案是多次调用 rand_si()
)。
xorshift*
由于最后的乘法,看起来表现得更好,所以它不容易发现由于播种不充分而导致的不良行为。
提示:将您的代码生成的数字与原始实现进行比较。这样您就可以确定您的实施是正确的。
geza 的回答完全正确,播种是罪魁祸首。使用标准的 64 位 PRNG 播种效果更好:
void seed(uint64_t s) {
std::mt19937_64 e(s);
s0 = __v4du{e(), e(), e(), e()};
s1 = __v4du{e(), e(), e(), e()};
}
上面两个人都错了。 xorshift+ 生成器即使在初始基数(种子)为 1、2、3 ......和其他最简单的值时也能正常工作。生成器仅在零值种子上失败。检查您的 64 位变量表示和二元运算符的正确工作。
我有以下代码(the xorshift128+
code from Wikipedia 修改为使用矢量类型):
#include <immintrin.h>
#include <climits>
__v8si rand_si() {
static auto s0 = __v4du{4, 8, 15, 16},
s1 = __v4du{23, 34, 42, 69};
auto x = s0, y = s1;
s0 = y;
x ^= x << 23;
s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
return (__v8si)(s1 + y);
}
#include <iostream>
#include <iomanip>
void foo() {
//Shuffle a bit. The result is much worse without this.
rand_si(); rand_si(); rand_si(); rand_si();
auto val = rand_si();
for (auto it = reinterpret_cast<int*>(&val);
it != reinterpret_cast<int*>(&val + 1);
++it)
std::cout << std::hex << std::setfill('0') << std::setw(8) << *it << ' ';
std::cout << '\n';
}
输出
09e2a657 000b8020 1504cc3b 00110040 1360ff2b 00150078 2a9998b7 00228080
每隔一个数字都非常小,none设置了前导位。另一方面,使用 xorshift* 代替:
__v8si rand_si() {
static auto x = __v4du{4, 8, 15, 16};
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
return x * (__v4du)_mm256_set1_epi64x(0x2545F4914F6CDD1D);
}
我得到了更好的输出
0889632e a938b990 1e8b2f79 832e26bd 11280868 2a22d676 275ca4b8 10954ef9
但是根据维基百科,xorshift+ 是一个很好的 PRNG,并且产生比 xorshift* 更好的伪随机性。那么,是我的 RNG 代码有错误,还是我用错了?
我认为您不应该通过查看随机生成器生成的 8 个数字来判断它。此外,生成器通常需要良好的播种(您的播种可以被认为是糟糕的 - 您的种子几乎所有位都以零开始。仅调用 rand_si()
几次不足以使位达到 "spread")。
所以我建议您使用适当的种子(例如,一个简单的解决方案是多次调用 rand_si()
)。
xorshift*
由于最后的乘法,看起来表现得更好,所以它不容易发现由于播种不充分而导致的不良行为。
提示:将您的代码生成的数字与原始实现进行比较。这样您就可以确定您的实施是正确的。
geza 的回答完全正确,播种是罪魁祸首。使用标准的 64 位 PRNG 播种效果更好:
void seed(uint64_t s) {
std::mt19937_64 e(s);
s0 = __v4du{e(), e(), e(), e()};
s1 = __v4du{e(), e(), e(), e()};
}
上面两个人都错了。 xorshift+ 生成器即使在初始基数(种子)为 1、2、3 ......和其他最简单的值时也能正常工作。生成器仅在零值种子上失败。检查您的 64 位变量表示和二元运算符的正确工作。