From 7dc076103ba34cce0c29628b0115f76a2fffaaa8 Mon Sep 17 00:00:00 2001 From: Julian Squires Date: Sat, 22 Aug 2020 09:39:51 -0230 Subject: [PATCH 1/2] Try not-too-hard to avoid aborting in benchmarks So, if we pick a modulus of 0, of course uniform_1 croaks. If our generator really is acceptably random, that should be pretty rare. We add another call, which still doesn't guarantee we don't get two zeros in a row, but if that happens, the PRNG is more likely to be broken or your luck was really bad. We could just bitwise-or in a 1, but I prefer having this canary. As evidence that I have tested this code too infrequently without rdrand, it turns out that PCG32 initialization without rdrand has been quite weak, all this time, and it's quite common to get 0 from uniform32 here as the modulus, much more than it should have been. --- c_src/stub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c_src/stub.c b/c_src/stub.c index b2d8a3a..957c884 100644 --- a/c_src/stub.c +++ b/c_src/stub.c @@ -50,7 +50,7 @@ int main(void) int main(void) { - uint32_t modulus = uniform32(); + uint32_t modulus = uniform32() || uniform32(); for (size_t i = 0; i < 10000000UL; ++i) uniform_1(NULL, 1, (const ERL_NIF_TERM[]){modulus}); return 0; From 97553429dfd54f2f2f5cb9addf764daa1507cb12 Mon Sep 17 00:00:00 2001 From: Julian Squires Date: Sat, 22 Aug 2020 09:47:57 -0230 Subject: [PATCH 2/2] Moderately improve PCG32 seeding without rdrand Without rdrand, benchmarks were aborting because the first value generated was often zero. Having high-quality seeding has never been a goal of this library, but this was pretty bad. So we mix in an initialization constant (from some other code I wrote using pcg32, which presumably took it from upstream PCG examples or a similar source). --- c_src/pcg32.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/c_src/pcg32.c b/c_src/pcg32.c index 0def108..d56e89a 100644 --- a/c_src/pcg32.c +++ b/c_src/pcg32.c @@ -35,26 +35,36 @@ #include "rdtsc.h" #endif +static __thread uint64_t state = 5573589319906701683ULL; + +static uint32_t pcg32(void) +{ + uint64_t oldstate = state; + state = oldstate * 6364136223846793005ULL; + uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + uint32_t rot = oldstate >> 59u; + uint32_t v = (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); + return v; +} + uint32_t uniform32(void) { static __thread bool seeded = false; - static __thread uint64_t state; if (unlikely(!seeded)) { + uint64_t t; #ifdef HAVE_RDRAND - state = rdrand64(); + t = rdrand64(); #else uint64_t u; - rdtsc(&state, &u); + rdtsc(&t, &u); #endif + pcg32(); + state += t; + pcg32(); seeded = true; } - uint64_t oldstate = state; - state = oldstate * 6364136223846793005ULL; - uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; - uint32_t rot = oldstate >> 59u; - uint32_t v = (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); - return v; + return pcg32(); } ERL_NIF_TERM uniform_1(ErlNifEnv *env,