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