mirror of https://github.com/postgres/postgres
Standardize on xoroshiro128** as our basic PRNG algorithm, eliminating a bunch of platform dependencies as well as fundamentally-obsolete PRNG code. In addition, this API replacement will ease replacing the algorithm again in future, should that become necessary. xoroshiro128** is a few percent slower than the drand48 family, but it can produce full-width 64-bit random values not only 48-bit, and it should be much more trustworthy. It's likely to be noticeably faster than the platform's random(), depending on which platform you are thinking about; and we can have non-global state vectors easily, unlike with random(). It is not cryptographically strong, but neither are the functions it replaces. Fabien Coelho, reviewed by Dean Rasheed, Aleksander Alekseev, and myself Discussion: https://postgr.es/m/alpine.DEB.2.22.394.2105241211230.165418@pseudopull/73/head
parent
f44ceb46ec
commit
3804539e48
@ -0,0 +1,247 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* Pseudo-Random Number Generator |
||||||
|
* |
||||||
|
* We use Blackman and Vigna's xoroshiro128** 1.0 algorithm |
||||||
|
* to have a small, fast PRNG suitable for generating reasonably |
||||||
|
* good-quality 64-bit data. This should not be considered |
||||||
|
* cryptographically strong, however. |
||||||
|
* |
||||||
|
* About these generators: https://prng.di.unimi.it/
|
||||||
|
* See also https://en.wikipedia.org/wiki/List_of_random_number_generators
|
||||||
|
* |
||||||
|
* Copyright (c) 2021, PostgreSQL Global Development Group |
||||||
|
* |
||||||
|
* src/common/pg_prng.c |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "c.h" |
||||||
|
|
||||||
|
#include <math.h> /* for ldexp() */ |
||||||
|
|
||||||
|
#include "common/pg_prng.h" |
||||||
|
#include "port/pg_bitutils.h" |
||||||
|
|
||||||
|
/* process-wide state vector */ |
||||||
|
pg_prng_state pg_global_prng_state; |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 64-bit rotate left |
||||||
|
*/ |
||||||
|
static inline uint64 |
||||||
|
rotl(uint64 x, int bits) |
||||||
|
{ |
||||||
|
return (x << bits) | (x >> (64 - bits)); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* The basic xoroshiro128** algorithm. |
||||||
|
* Generates and returns a 64-bit uniformly distributed number, |
||||||
|
* updating the state vector for next time. |
||||||
|
* |
||||||
|
* Note: the state vector must not be all-zeroes, as that is a fixed point. |
||||||
|
*/ |
||||||
|
static uint64 |
||||||
|
xoroshiro128ss(pg_prng_state *state) |
||||||
|
{ |
||||||
|
uint64 s0 = state->s0, |
||||||
|
sx = state->s1 ^ s0, |
||||||
|
val = rotl(s0 * 5, 7) * 9; |
||||||
|
|
||||||
|
/* update state */ |
||||||
|
state->s0 = rotl(s0, 24) ^ sx ^ (sx << 16); |
||||||
|
state->s1 = rotl(sx, 37); |
||||||
|
|
||||||
|
return val; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* We use this generator just to fill the xoroshiro128** state vector |
||||||
|
* from a 64-bit seed. |
||||||
|
*/ |
||||||
|
static uint64 |
||||||
|
splitmix64(uint64 *state) |
||||||
|
{ |
||||||
|
/* state update */ |
||||||
|
uint64 val = (*state += UINT64CONST(0x9E3779B97f4A7C15)); |
||||||
|
|
||||||
|
/* value extraction */ |
||||||
|
val = (val ^ (val >> 30)) * UINT64CONST(0xBF58476D1CE4E5B9); |
||||||
|
val = (val ^ (val >> 27)) * UINT64CONST(0x94D049BB133111EB); |
||||||
|
|
||||||
|
return val ^ (val >> 31); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the PRNG state from a 64-bit integer, |
||||||
|
* taking care that we don't produce all-zeroes. |
||||||
|
*/ |
||||||
|
void |
||||||
|
pg_prng_seed(pg_prng_state *state, uint64 seed) |
||||||
|
{ |
||||||
|
state->s0 = splitmix64(&seed); |
||||||
|
state->s1 = splitmix64(&seed); |
||||||
|
/* Let's just make sure we didn't get all-zeroes */ |
||||||
|
(void) pg_prng_seed_check(state); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the PRNG state from a double in the range [-1.0, 1.0], |
||||||
|
* taking care that we don't produce all-zeroes. |
||||||
|
*/ |
||||||
|
void |
||||||
|
pg_prng_fseed(pg_prng_state *state, double fseed) |
||||||
|
{ |
||||||
|
/* Assume there's about 52 mantissa bits; the sign contributes too. */ |
||||||
|
int64 seed = ((double) ((UINT64CONST(1) << 52) - 1)) * fseed; |
||||||
|
|
||||||
|
pg_prng_seed(state, (uint64) seed); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Validate a PRNG seed value. |
||||||
|
*/ |
||||||
|
bool |
||||||
|
pg_prng_seed_check(pg_prng_state *state) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* If the seeding mechanism chanced to produce all-zeroes, insert |
||||||
|
* something nonzero. Anything would do; use Knuth's LCG parameters. |
||||||
|
*/ |
||||||
|
if (unlikely(state->s0 == 0 && state->s1 == 0)) |
||||||
|
{ |
||||||
|
state->s0 = UINT64CONST(0x5851F42D4C957F2D); |
||||||
|
state->s1 = UINT64CONST(0x14057B7EF767814F); |
||||||
|
} |
||||||
|
|
||||||
|
/* As a convenience for the pg_prng_strong_seed macro, return true */ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random uint64 uniformly from the range [0, PG_UINT64_MAX]. |
||||||
|
*/ |
||||||
|
uint64 |
||||||
|
pg_prng_uint64(pg_prng_state *state) |
||||||
|
{ |
||||||
|
return xoroshiro128ss(state); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random uint64 uniformly from the range [rmin, rmax]. |
||||||
|
* If the range is empty, rmin is always produced. |
||||||
|
*/ |
||||||
|
uint64 |
||||||
|
pg_prng_uint64_range(pg_prng_state *state, uint64 rmin, uint64 rmax) |
||||||
|
{ |
||||||
|
uint64 val; |
||||||
|
|
||||||
|
if (likely(rmax > rmin)) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Use bitmask rejection method to generate an offset in 0..range. |
||||||
|
* Each generated val is less than twice "range", so on average we |
||||||
|
* should not have to iterate more than twice. |
||||||
|
*/ |
||||||
|
uint64 range = rmax - rmin; |
||||||
|
uint32 rshift = 63 - pg_leftmost_one_pos64(range); |
||||||
|
|
||||||
|
do |
||||||
|
{ |
||||||
|
val = xoroshiro128ss(state) >> rshift; |
||||||
|
} while (val > range); |
||||||
|
} |
||||||
|
else |
||||||
|
val = 0; |
||||||
|
|
||||||
|
return rmin + val; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random int64 uniformly from the range [PG_INT64_MIN, PG_INT64_MAX]. |
||||||
|
*/ |
||||||
|
int64 |
||||||
|
pg_prng_int64(pg_prng_state *state) |
||||||
|
{ |
||||||
|
return (int64) xoroshiro128ss(state); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random int64 uniformly from the range [0, PG_INT64_MAX]. |
||||||
|
*/ |
||||||
|
int64 |
||||||
|
pg_prng_int64p(pg_prng_state *state) |
||||||
|
{ |
||||||
|
return (int64) (xoroshiro128ss(state) & UINT64CONST(0x7FFFFFFFFFFFFFFF)); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random uint32 uniformly from the range [0, PG_UINT32_MAX]. |
||||||
|
*/ |
||||||
|
uint32 |
||||||
|
pg_prng_uint32(pg_prng_state *state) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Although xoroshiro128** is not known to have any weaknesses in |
||||||
|
* randomness of low-order bits, we prefer to use the upper bits of its |
||||||
|
* result here and below. |
||||||
|
*/ |
||||||
|
uint64 v = xoroshiro128ss(state); |
||||||
|
|
||||||
|
return (uint32) (v >> 32); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random int32 uniformly from the range [PG_INT32_MIN, PG_INT32_MAX]. |
||||||
|
*/ |
||||||
|
int32 |
||||||
|
pg_prng_int32(pg_prng_state *state) |
||||||
|
{ |
||||||
|
uint64 v = xoroshiro128ss(state); |
||||||
|
|
||||||
|
return (int32) (v >> 32); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random int32 uniformly from the range [0, PG_INT32_MAX]. |
||||||
|
*/ |
||||||
|
int32 |
||||||
|
pg_prng_int32p(pg_prng_state *state) |
||||||
|
{ |
||||||
|
uint64 v = xoroshiro128ss(state); |
||||||
|
|
||||||
|
return (int32) (v >> 33); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random double uniformly from the range [0.0, 1.0). |
||||||
|
* |
||||||
|
* Note: if you want a result in the range (0.0, 1.0], the standard way |
||||||
|
* to get that is "1.0 - pg_prng_double(state)". |
||||||
|
*/ |
||||||
|
double |
||||||
|
pg_prng_double(pg_prng_state *state) |
||||||
|
{ |
||||||
|
uint64 v = xoroshiro128ss(state); |
||||||
|
|
||||||
|
/*
|
||||||
|
* As above, assume there's 52 mantissa bits in a double. This result |
||||||
|
* could round to 1.0 if double's precision is less than that; but we |
||||||
|
* assume IEEE float arithmetic elsewhere in Postgres, so this seems OK. |
||||||
|
*/ |
||||||
|
return ldexp((double) (v >> (64 - 52)), -52); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Select a random boolean value. |
||||||
|
*/ |
||||||
|
bool |
||||||
|
pg_prng_bool(pg_prng_state *state) |
||||||
|
{ |
||||||
|
uint64 v = xoroshiro128ss(state); |
||||||
|
|
||||||
|
return (bool) (v >> 63); |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* Pseudo-Random Number Generator |
||||||
|
* |
||||||
|
* Copyright (c) 2021, PostgreSQL Global Development Group |
||||||
|
* |
||||||
|
* src/include/common/pg_prng.h |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
#ifndef PG_PRNG_H |
||||||
|
#define PG_PRNG_H |
||||||
|
|
||||||
|
/*
|
||||||
|
* State vector for PRNG generation. Callers should treat this as an |
||||||
|
* opaque typedef, but we expose its definition to allow it to be |
||||||
|
* embedded in other structs. |
||||||
|
*/ |
||||||
|
typedef struct pg_prng_state |
||||||
|
{ |
||||||
|
uint64 s0, |
||||||
|
s1; |
||||||
|
} pg_prng_state; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Callers not needing local PRNG series may use this global state vector, |
||||||
|
* after initializing it with one of the pg_prng_...seed functions. |
||||||
|
*/ |
||||||
|
extern PGDLLIMPORT pg_prng_state pg_global_prng_state; |
||||||
|
|
||||||
|
extern void pg_prng_seed(pg_prng_state *state, uint64 seed); |
||||||
|
extern void pg_prng_fseed(pg_prng_state *state, double fseed); |
||||||
|
extern bool pg_prng_seed_check(pg_prng_state *state); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the PRNG state from the pg_strong_random source, |
||||||
|
* taking care that we don't produce all-zeroes. If this returns false, |
||||||
|
* caller should initialize the PRNG state from some other random seed, |
||||||
|
* using pg_prng_[f]seed. |
||||||
|
* |
||||||
|
* We implement this as a macro, so that the pg_strong_random() call is |
||||||
|
* in the caller. If it were in pg_prng.c, programs using pg_prng.c |
||||||
|
* but not needing strong seeding would nonetheless be forced to pull in |
||||||
|
* pg_strong_random.c and thence OpenSSL. |
||||||
|
*/ |
||||||
|
#define pg_prng_strong_seed(state) \ |
||||||
|
(pg_strong_random((void *) (state), sizeof(pg_prng_state)) ? \
|
||||||
|
pg_prng_seed_check(state) : false) |
||||||
|
|
||||||
|
extern uint64 pg_prng_uint64(pg_prng_state *state); |
||||||
|
extern uint64 pg_prng_uint64_range(pg_prng_state *state, uint64 rmin, uint64 rmax); |
||||||
|
extern int64 pg_prng_int64(pg_prng_state *state); |
||||||
|
extern int64 pg_prng_int64p(pg_prng_state *state); |
||||||
|
extern uint32 pg_prng_uint32(pg_prng_state *state); |
||||||
|
extern int32 pg_prng_int32(pg_prng_state *state); |
||||||
|
extern int32 pg_prng_int32p(pg_prng_state *state); |
||||||
|
extern double pg_prng_double(pg_prng_state *state); |
||||||
|
extern bool pg_prng_bool(pg_prng_state *state); |
||||||
|
|
||||||
|
#endif /* PG_PRNG_H */ |
@ -1,136 +0,0 @@ |
|||||||
/*-------------------------------------------------------------------------
|
|
||||||
* |
|
||||||
* erand48.c |
|
||||||
* |
|
||||||
* This file supplies pg_erand48() and related functions, which except |
|
||||||
* for the names are just like the POSIX-standard erand48() family. |
|
||||||
* (We don't supply the full set though, only the ones we have found use |
|
||||||
* for in Postgres. In particular, we do *not* implement lcong48(), so |
|
||||||
* that there is no need for the multiplier and addend to be variable.) |
|
||||||
* |
|
||||||
* We used to test for an operating system version rather than |
|
||||||
* unconditionally using our own, but (1) some versions of Cygwin have a |
|
||||||
* buggy erand48() that always returns zero and (2) as of 2011, glibc's |
|
||||||
* erand48() is strangely coded to be almost-but-not-quite thread-safe, |
|
||||||
* which doesn't matter for the backend but is important for pgbench. |
|
||||||
* |
|
||||||
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
|
||||||
* |
|
||||||
* Portions Copyright (c) 1993 Martin Birgmeier |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* You may redistribute unmodified or modified versions of this source |
|
||||||
* code provided that the above copyright notice and this and the |
|
||||||
* following conditions are retained. |
|
||||||
* |
|
||||||
* This software is provided ``as is'', and comes with no warranties |
|
||||||
* of any kind. I shall in no event be liable for anything that happens |
|
||||||
* to anyone/anything when using this software. |
|
||||||
* |
|
||||||
* IDENTIFICATION |
|
||||||
* src/port/erand48.c |
|
||||||
* |
|
||||||
*------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "c.h" |
|
||||||
|
|
||||||
#include <math.h> |
|
||||||
|
|
||||||
/* These values are specified by POSIX */ |
|
||||||
#define RAND48_MULT UINT64CONST(0x0005deece66d) |
|
||||||
#define RAND48_ADD UINT64CONST(0x000b) |
|
||||||
|
|
||||||
/* POSIX specifies 0x330e's use in srand48, but the other bits are arbitrary */ |
|
||||||
#define RAND48_SEED_0 (0x330e) |
|
||||||
#define RAND48_SEED_1 (0xabcd) |
|
||||||
#define RAND48_SEED_2 (0x1234) |
|
||||||
|
|
||||||
static unsigned short _rand48_seed[3] = { |
|
||||||
RAND48_SEED_0, |
|
||||||
RAND48_SEED_1, |
|
||||||
RAND48_SEED_2 |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Advance the 48-bit value stored in xseed[] to the next "random" number. |
|
||||||
* |
|
||||||
* Also returns the value of that number --- without masking it to 48 bits. |
|
||||||
* If caller uses the result, it must mask off the bits it wants. |
|
||||||
*/ |
|
||||||
static uint64 |
|
||||||
_dorand48(unsigned short xseed[3]) |
|
||||||
{ |
|
||||||
/*
|
|
||||||
* We do the arithmetic in uint64; any type wider than 48 bits would work. |
|
||||||
*/ |
|
||||||
uint64 in; |
|
||||||
uint64 out; |
|
||||||
|
|
||||||
in = (uint64) xseed[2] << 32 | (uint64) xseed[1] << 16 | (uint64) xseed[0]; |
|
||||||
|
|
||||||
out = in * RAND48_MULT + RAND48_ADD; |
|
||||||
|
|
||||||
xseed[0] = out & 0xFFFF; |
|
||||||
xseed[1] = (out >> 16) & 0xFFFF; |
|
||||||
xseed[2] = (out >> 32) & 0xFFFF; |
|
||||||
|
|
||||||
return out; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Generate a random floating-point value using caller-supplied state. |
|
||||||
* Values are uniformly distributed over the interval [0.0, 1.0). |
|
||||||
*/ |
|
||||||
double |
|
||||||
pg_erand48(unsigned short xseed[3]) |
|
||||||
{ |
|
||||||
uint64 x = _dorand48(xseed); |
|
||||||
|
|
||||||
return ldexp((double) (x & UINT64CONST(0xFFFFFFFFFFFF)), -48); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Generate a random non-negative integral value using internal state. |
|
||||||
* Values are uniformly distributed over the interval [0, 2^31). |
|
||||||
*/ |
|
||||||
long |
|
||||||
pg_lrand48(void) |
|
||||||
{ |
|
||||||
uint64 x = _dorand48(_rand48_seed); |
|
||||||
|
|
||||||
return (x >> 17) & UINT64CONST(0x7FFFFFFF); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Generate a random signed integral value using caller-supplied state. |
|
||||||
* Values are uniformly distributed over the interval [-2^31, 2^31). |
|
||||||
*/ |
|
||||||
long |
|
||||||
pg_jrand48(unsigned short xseed[3]) |
|
||||||
{ |
|
||||||
uint64 x = _dorand48(xseed); |
|
||||||
|
|
||||||
return (int32) ((x >> 16) & UINT64CONST(0xFFFFFFFF)); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the internal state using the given seed. |
|
||||||
* |
|
||||||
* Per POSIX, this uses only 32 bits from "seed" even if "long" is wider. |
|
||||||
* Hence, the set of possible seed values is smaller than it could be. |
|
||||||
* Better practice is to use caller-supplied state and initialize it with |
|
||||||
* random bits obtained from a high-quality source of random bits. |
|
||||||
* |
|
||||||
* Note: POSIX specifies a function seed48() that allows all 48 bits |
|
||||||
* of the internal state to be set, but we don't currently support that. |
|
||||||
*/ |
|
||||||
void |
|
||||||
pg_srand48(long seed) |
|
||||||
{ |
|
||||||
_rand48_seed[0] = RAND48_SEED_0; |
|
||||||
_rand48_seed[1] = (unsigned short) seed; |
|
||||||
_rand48_seed[2] = (unsigned short) (seed >> 16); |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
/*-------------------------------------------------------------------------
|
|
||||||
* |
|
||||||
* random.c |
|
||||||
* random() wrapper |
|
||||||
* |
|
||||||
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California |
|
||||||
* |
|
||||||
* |
|
||||||
* IDENTIFICATION |
|
||||||
* src/port/random.c |
|
||||||
* |
|
||||||
*------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "c.h" |
|
||||||
|
|
||||||
#include <math.h> |
|
||||||
|
|
||||||
|
|
||||||
long |
|
||||||
random(void) |
|
||||||
{ |
|
||||||
return pg_lrand48(); |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
/*-------------------------------------------------------------------------
|
|
||||||
* |
|
||||||
* srandom.c |
|
||||||
* srandom() wrapper |
|
||||||
* |
|
||||||
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California |
|
||||||
* |
|
||||||
* |
|
||||||
* IDENTIFICATION |
|
||||||
* src/port/srandom.c |
|
||||||
* |
|
||||||
*------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "c.h" |
|
||||||
|
|
||||||
#include <math.h> |
|
||||||
|
|
||||||
|
|
||||||
void |
|
||||||
srandom(unsigned int seed) |
|
||||||
{ |
|
||||||
pg_srand48((long int) seed); |
|
||||||
} |
|
Loading…
Reference in new issue