競プロ 乱数 速度調査

疑似乱数の生成速度について軽く調べた。まず結果から紹介する。それぞれ $10^8$ 回乱数を生成してかかった時間をAtCoderのコードテストで計測した。実験コードはここ

Name Output bit Time
LCG32 32bit 114ms
mt19937 32bit 325ms
mt19937_64 64bit 389ms
xorshift32 32bit 192ms
xorshift64 64bit 193ms
xoshiro128++ 32bit 249ms
xoshiro128** 32bit 238ms
xoshiro256++ 64bit 249ms
xoshiro256** 64bit 238ms
pcg32 32bit 134ms
pcg32_fast 32bit 105ms
pcg64 64bit 202ms
pcg64_fast 64bit 157ms
Mwc128XXA32 32bit 92ms
Mwc256XXA64 64bit 108ms

Output bitで分類すると次のようになる。

Name Output bit Time
LCG32 32bit 114ms
mt19937 32bit 325ms
xorshift32 32bit 192ms
xoshiro128++ 32bit 249ms
xoshiro128** 32bit 238ms
pcg32 32bit 134ms
pcg32_fast 32bit 105ms
Mwc128XXA32 32bit 92ms
Name Output bit Time
mt19937_64 64bit 389ms
xorshift64 64bit 193ms
xoshiro256++ 64bit 249ms
xoshiro256** 64bit 238ms
pcg64 64bit 202ms
pcg64_fast 64bit 157ms
Mwc256XXA64 64bit 108ms

LCG (線形合同法)

$X = (A \times X + B) \bmod M$ という形で表されるやつ。パラメーターはwikipediaから(あまりよくないパラメーターらしい)

// Reference:
// https://ja.wikipedia.org/wiki/%E7%B7%9A%E5%BD%A2%E5%90%88%E5%90%8C%E6%B3%95
namespace lcg32 {

inline static u32 state = 12345;

u32 next() { return state = 1'103'515'245 * state + 12'345; }

}  // namespace lcg32

mt19937 (メルセンヌ・ツイスター)

C++のSTLのmt19937 / mt19937_64をそのまま

// Reference:
// https://cpprefjp.github.io/reference/random/mt19937.html
namespace mt19937 {

inline static std::mt19937 engine(12345);

u32 next() { return (u32)engine(); }

}  // namespace mt19937

// Reference:
// https://cpprefjp.github.io/reference/random/mt19937_64.html
namespace mt19937_64 {

inline static std::mt19937_64 engine(12345);

u64 next() { return (u64)engine(); }

}  // namespace mt19937_64

xorshift

主観だと競プロで一番使われていそうなやつ。実装はwikipediaから殆どそのまま

// Reference:
// https://ja.wikipedia.org/wiki/Xorshift
namespace xorshift32 {

inline static u32 a = 12345;

u32 next() {
    u32 x = a;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    return a = x;
}

}  // namespace xorshift32

// Reference:
// https://ja.wikipedia.org/wiki/Xorshift
namespace xorshift64 {

inline static u64 a = 12345;

u64 next() {
    u64 x = a;
    x ^= x << 13;
    x ^= x >> 7;
    x ^= x << 17;
    return a = x;
}

}  // namespace xorshift64

xoshiro++ / xoshiro**

https://prng.di.unimi.it/ に詳しい紹介がある。速くて質もいいらしい。実装はこのサイトから殆どそのまま

// Reference:
// https://prng.di.unimi.it/
namespace xoshiro128plusplus {

inline static u32 s[4] = {123, 234, 345, 567};

u32 next() {
    const u32 result_starstar = std::rotl(s[0] + s[3], 7) + s[0];

    const u32 t = s[1] << 9;

    s[2] ^= s[0];
    s[3] ^= s[1];
    s[1] ^= s[2];
    s[0] ^= s[3];

    s[2] ^= t;

    s[3] = std::rotl(s[3], 11);

    return result_starstar;
}

}  // namespace xoshiro128plusplus

// Reference:
// https://prng.di.unimi.it/
namespace xoshiro128starstar {

inline static u32 s[4] = {123, 234, 345, 567};

u32 next() {
    const u32 result_starstar = std::rotl(s[1] * 5, 7) * 9;

    const u32 t = s[1] << 9;

    s[2] ^= s[0];
    s[3] ^= s[1];
    s[1] ^= s[2];
    s[0] ^= s[3];

    s[2] ^= t;

    s[3] = std::rotl(s[3], 11);

    return result_starstar;
}

}  // namespace xoshiro128starstar

// Reference:
// https://prng.di.unimi.it/
namespace xoshiro256plusplus {

inline static u64 s[4] = {123, 234, 345, 567};

u64 next() {
    const u64 result_starstar = std::rotl(s[0] + s[3], 23) + s[0];

    const u64 t = s[1] << 17;

    s[2] ^= s[0];
    s[3] ^= s[1];
    s[1] ^= s[2];
    s[0] ^= s[3];

    s[2] ^= t;

    s[3] = std::rotl(s[3], 45);

    return result_starstar;
}

}  // namespace xoshiro256plusplus

// Reference:
// https://prng.di.unimi.it/
namespace xoshiro256starstar {

inline static u64 s[4] = {123, 234, 345, 567};

u64 next() {
    const u64 result_starstar = std::rotl(s[1] * 5, 7) * 9;

    const u64 t = s[1] << 17;

    s[2] ^= s[0];
    s[3] ^= s[1];
    s[1] ^= s[2];
    s[0] ^= s[3];

    s[2] ^= t;

    s[3] = std::rotl(s[3], 45);

    return result_starstar;
}

}  // namespace xoshiro256starstar

PGC

https://www.pcg-random.org/index.html に詳しい紹介がある。速くて質もいいらしい。

標準のpcg32 / pcg64と、ストリーム機能(とちょっと短い周期)の代わりに速度に特化したpcg32_fast / pcg64_fastがある。

pcg32 / pgc32_fast は wikipedia の実装を参考に、 pcg64 / pcg64_fast はrustの rand_pcg crateの実装を参考にした。

// Reference:
// https://www.pcg-random.org/download.html
// https://en.wikipedia.org/wiki/Permuted_congruential_generator
namespace pcg32 {

const u64 MULT = 6364136223846793005;
const u64 INC = 1442695040888963407;

inline static u64 state = 123;

u32 next() {
    u64 x = state;
    state = state * MULT + INC;

    u32 count = x >> 59;
    x ^= x >> 18;

    return std::rotr(u32(x >> 27), count);
}

}  // namespace pcg32

// Reference:
// https://www.pcg-random.org/download.html
// https://en.wikipedia.org/wiki/Permuted_congruential_generator
namespace pcg32_fast {

const u64 MULT = 6364136223846793005;

inline static u64 state = 123;

u32 next() {
    u64 x = state;
    state = state * MULT;

    u32 count = x >> 61;
    x ^= x >> 22;

    return (u32)(x >> (22 + count));
}

}  // namespace pcg32_fast

// Reference:
// https://www.pcg-random.org/download.html
// https://crates.io/crates/rand_pcg
namespace pcg64 {

const u128 MULT = u128(2549297995355413924) << 64 | u128(4865540595714422341);
const u128 INC = u128(6364136223846793005) << 64 | u128(1442695040888963407);

inline static u128 state = 123;

u64 next() {
    u128 x = state;
    state = state * MULT + INC;

    u32 rot = x >> 122;
    return std::rotr((u64)(x >> 64) ^ (u64)x, rot);
}

}  // namespace pcg64

// Reference:
// https://www.pcg-random.org/download.html
// https://crates.io/crates/rand_pcg
namespace pcg64_fast {

const u128 MULT = u128(2549297995355413924) << 64 | u128(4865540595714422341);

inline static u128 state = 123;

u64 next() {
    u128 x = state;
    state = state * MULT;

    u32 rot = x >> 122;
    return std::rotr((u64)(x >> 64) ^ (u64)x, rot);
}

}  // namespace pcg64_fast

Mwc128xxa32 / Mwc256xxa64

このブログで紹介されている。質が良くてPCGより速いらしい。

実装は githubから殆どそのまま。変更点はx1とcをまとめている。

// Reference:
// https://tom-kaitchuck.medium.com/designing-a-new-prng-1c4ffd27124d
// https://github.com/tkaitchuck/Mwc256XXA64
namespace mwc128xxa32 {

const u32 MULT = 3487286589;

inline static u32 x2 = 12345;
inline static u32 x3 = 0xcafef00d;
inline static u64 c_x1 = u64(0xd15ea5e5) << 32 | 23456;

u32 next() {
    u64 x = (u64)(x3)*MULT;
    u32 result = (x3 ^ x2) + ((u32)(c_x1) ^ (u32)(x >> 32));
    x3 = x2;
    x2 = (u32)(c_x1);
    c_x1 = x + (c_x1 >> 32);
    return result;
}

}  // namespace mwc128xxa32

// Reference:
// https://tom-kaitchuck.medium.com/designing-a-new-prng-1c4ffd27124d
// https://github.com/tkaitchuck/Mwc256XXA64
namespace mwc256xxa64 {

const u64 MULT = 0xfeb3'4465'7c0a'f413;

inline static u64 x2 = 12345;
inline static u64 x3 = 0xcafef00dd15ea5e5;
inline static u128 c_x1 = u128(0x1405'7B7E'F767'814F) << 64 | 23456;

u64 next() {
    u128 x = (u128)(x3)*MULT;
    u64 result = (x3 ^ x2) + ((u64)(c_x1) ^ (u64)(x >> 64));
    x3 = x2;
    x2 = (u64)(c_x1);
    c_x1 = x + (c_x1 >> 64);
    return result;
}

}  // namespace mwc256xxa64