rand

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2022 License: MPL-2.0 Imports: 5 Imported by: 30

README

rand PkgGoDev CI

Fast, high quality alternative to math/rand and golang.org/x/exp/rand.

Compared to these packages, pgregory.net/rand:

  • is API-compatible with all *rand.Rand methods,
  • is significantly faster, while providing state-of-the-art generator quality,
  • has simpler generator initialization:
    • rand.New() instead of rand.New(rand.NewSource(time.Now().UnixNano()))
    • rand.New(1) instead of rand.New(rand.NewSource(1))
  • is deliberately not providing top-level functions like Float64() or Int() and the Source interface.

Benchmarks

All benchmarks were run on Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz (frequency-locked), linux/amd64.

Compared to math/rand:

name                    old time/op    new time/op    delta
Rand_New-8                19.4µs ± 0%     0.1µs ± 4%   -99.54%  (p=0.000 n=8+9)
Rand_ExpFloat64-8         12.1ns ± 0%     8.6ns ± 0%   -28.92%  (p=0.000 n=10+8)
Rand_Float32-8            10.3ns ± 2%     3.4ns ± 1%   -66.60%  (p=0.000 n=10+9)
Rand_Float64-8            7.07ns ± 2%    4.08ns ± 0%   -42.24%  (p=0.000 n=10+9)
Rand_Int-8                6.40ns ± 2%    3.82ns ± 0%   -40.20%  (p=0.000 n=10+9)
Rand_Int31-8              6.27ns ± 2%    2.64ns ± 0%   -57.90%  (p=0.000 n=10+8)
Rand_Int31n-8             16.9ns ± 2%     4.0ns ± 0%   -76.14%  (p=0.000 n=10+8)
Rand_Int31n_Big-8         16.9ns ± 1%     4.0ns ± 0%   -76.07%  (p=0.000 n=10+9)
Rand_Int63-8              6.19ns ± 2%    3.82ns ± 0%   -38.31%  (p=0.000 n=10+9)
Rand_Int63n-8             42.5ns ± 2%     5.6ns ± 0%   -86.86%  (p=0.000 n=10+8)
Rand_Int63n_Big-8         42.5ns ± 1%     9.5ns ± 0%   -77.69%  (p=0.000 n=10+10)
Rand_Intn-8               19.4ns ± 2%     5.6ns ± 0%   -71.29%  (p=0.000 n=10+9)
Rand_Intn_Big-8           45.2ns ± 1%     9.5ns ± 0%   -79.02%  (p=0.000 n=10+9)
Rand_NormFloat64-8        13.3ns ± 0%     8.7ns ± 0%   -34.79%  (p=0.000 n=10+10)
Rand_Perm-8               1.28µs ± 1%    0.39µs ± 0%   -69.35%  (p=0.000 n=9+10)
Rand_Read-8                455ns ± 1%     134ns ± 0%   -70.52%  (p=0.000 n=10+10)
Rand_Seed-8               17.5µs ± 1%     0.0µs ± 0%   -99.74%  (p=0.000 n=9+9)
Rand_Shuffle-8             696ns ± 2%     409ns ± 0%   -41.32%  (p=0.000 n=10+9)
Rand_ShuffleOverhead-8     576ns ± 2%     303ns ± 0%   -47.30%  (p=0.000 n=10+9)
Rand_Uint32-8             6.15ns ± 2%    2.61ns ± 1%   -57.56%  (p=0.000 n=10+9)
Rand_Uint64-8             8.57ns ± 2%    3.87ns ± 0%   -54.88%  (p=0.000 n=10+9)

name                    old alloc/op   new alloc/op   delta
Rand_New-8                5.42kB ± 0%    0.05kB ± 0%   -99.12%  (p=0.000 n=10+10)
Rand_Perm-8                 416B ± 0%      416B ± 0%      ~     (all equal)

name                    old allocs/op  new allocs/op  delta
Rand_New-8                  2.00 ± 0%      1.00 ± 0%   -50.00%  (p=0.000 n=10+10)
Rand_Perm-8                 1.00 ± 0%      1.00 ± 0%      ~     (all equal)

name                    old speed      new speed      delta
Rand_Read-8              563MB/s ± 1%  1908MB/s ± 0%  +239.15%  (p=0.000 n=10+10)
Rand_Uint32-8            650MB/s ± 2%  1531MB/s ± 1%  +135.57%  (p=0.000 n=10+9)
Rand_Uint64-8            934MB/s ± 2%  2069MB/s ± 0%  +121.60%  (p=0.000 n=10+9)
Compared to golang.org/x/exp/rand:
name                    old time/op    new time/op    delta
Rand_New-8                95.2ns ± 1%    88.9ns ± 4%    -6.68%  (p=0.000 n=10+9)
Rand_ExpFloat64-8         12.3ns ± 0%     8.6ns ± 0%   -30.56%  (p=0.000 n=10+8)
Rand_Float32-8            13.5ns ± 1%     3.4ns ± 1%   -74.38%  (p=0.000 n=9+9)
Rand_Float64-8            11.1ns ± 5%     4.1ns ± 0%   -63.29%  (p=0.000 n=10+9)
Rand_Int-8                7.37ns ± 5%    3.82ns ± 0%   -48.12%  (p=0.000 n=10+9)
Rand_Int31-8              7.10ns ± 4%    2.64ns ± 0%   -62.82%  (p=0.000 n=10+8)
Rand_Int31n-8             26.1ns ± 0%     4.0ns ± 0%   -84.49%  (p=0.000 n=10+8)
Rand_Int31n_Big-8         26.0ns ± 1%     4.0ns ± 0%   -84.50%  (p=0.000 n=9+9)
Rand_Int63-8              6.92ns ± 1%    3.82ns ± 0%   -44.82%  (p=0.000 n=9+9)
Rand_Int63n-8             26.0ns ± 1%     5.6ns ± 0%   -78.55%  (p=0.000 n=9+8)
Rand_Int63n_Big-8         39.8ns ± 0%     9.5ns ± 0%   -76.15%  (p=0.000 n=8+10)
Rand_Intn-8               26.0ns ± 1%     5.6ns ± 0%   -78.55%  (p=0.000 n=9+9)
Rand_Intn_Big-8           39.9ns ± 1%     9.5ns ± 0%   -76.21%  (p=0.000 n=10+9)
Rand_NormFloat64-8        14.0ns ± 0%     8.7ns ± 0%   -38.03%  (p=0.000 n=9+10)
Rand_Perm-8               1.42µs ± 1%    0.39µs ± 0%   -72.23%  (p=0.000 n=9+10)
Rand_Read-8                467ns ± 0%     134ns ± 0%   -71.29%  (p=0.000 n=9+10)
Rand_Seed-8               6.28ns ± 6%   46.18ns ± 0%  +635.79%  (p=0.000 n=10+9)
Rand_Shuffle-8            1.40µs ± 1%    0.41µs ± 0%   -70.75%  (p=0.000 n=9+9)
Rand_ShuffleOverhead-8    1.28µs ± 0%    0.30µs ± 0%   -76.26%  (p=0.000 n=10+9)
Rand_Uint32-8             6.70ns ± 0%    2.61ns ± 1%   -61.02%  (p=0.000 n=10+9)
Rand_Uint64-8             6.71ns ± 0%    3.87ns ± 0%   -42.35%  (p=0.000 n=10+9)
Rand_Uint64n-8            25.0ns ± 1%     5.6ns ± 0%   -77.59%  (p=0.000 n=10+10)
Rand_Uint64n_Big-8        40.0ns ± 1%     9.2ns ± 1%   -76.99%  (p=0.000 n=9+10)
Rand_MarshalBinary-8      36.7ns ± 2%     6.1ns ± 0%   -83.27%  (p=0.000 n=10+10)
Rand_UnmarshalBinary-8    5.31ns ± 0%    6.14ns ± 0%   +15.63%  (p=0.000 n=9+10)

name                    old alloc/op   new alloc/op   delta
Rand_New-8                 48.0B ± 0%     48.0B ± 0%      ~     (all equal)
Rand_Perm-8                 416B ± 0%      416B ± 0%      ~     (all equal)
Rand_MarshalBinary-8       16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=10+10)
Rand_UnmarshalBinary-8     0.00B          0.00B           ~     (all equal)

name                    old allocs/op  new allocs/op  delta
Rand_New-8                  2.00 ± 0%      1.00 ± 0%   -50.00%  (p=0.000 n=10+10)
Rand_Perm-8                 1.00 ± 0%      1.00 ± 0%      ~     (all equal)
Rand_MarshalBinary-8        1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)
Rand_UnmarshalBinary-8      0.00           0.00           ~     (all equal)

name                    old speed      new speed      delta
Rand_Read-8              548MB/s ± 0%  1908MB/s ± 0%  +248.24%  (p=0.000 n=9+10)
Rand_Uint32-8            597MB/s ± 0%  1531MB/s ± 1%  +156.55%  (p=0.000 n=10+9)
Rand_Uint64-8           1.19GB/s ± 0%  2.07GB/s ± 0%   +73.45%  (p=0.000 n=10+9)

FAQ

Why did you write this?

math/rand is both slow and not up to standards (1, 2) in terms of quality (but can not be changed because of Go 1 compatibility promise). golang.org/x/exp/rand fixes some (but not all) quality issues, without improving the speed, and it seems that there is no active development happening there.

How does this thing work?

This package builds on 3 primitives: raw 64-bit generation using sfc64, floating-point generation using floating-point multiplication, and integer generation in range using 32.64 or 64.128 fixed-point multiplication.

Why is it fast?

The primitives selected are (as far as I am aware) about as fast as you can go without sacrificing quality. On top of that, it is mainly making sure the compiler is able to inline code, and a couple of micro-optimizations.

Why no Source?

In Go (but not in C++ or Rust) it is a costly abstraction that provides no real value. How often do you use a non-default Source with math/rand?

Why no top-level functions?

Dislike for global mutable state. Also, without some kind of thread-local state they are very slow (because global state needs to be mutex-protected). If you like the convenience of top-level functions, math/rand is a fine choice.

Why sfc64?

I like it. It has withstood the test of time, with no known flaws or weaknesses despite a lot of effort and CPU-hours spent on finding them. Also, it provides guarantees about period length and distance between generators seeded with different seeds. And it is fast.

Why not...
...pcg?

A bit slow. Otherwise, pcg64dxsm is probably a fine choice.

...xoshiro/xoroshiro?

Quite a bit of controversy and people finding weaknesses in variants of this design. Did you know that xoshiro256**, which author describes as an "all-purpose, rock-solid generator" that "passes all tests we are aware of", fails them in seconds if you multiply the output by 57?

...splitmix?

With 64-bit state and 64-bit output, splitmix64 outputs every 64-bit number exactly once over its 2^64 period — in other words, the probability of generating the same number is 0. A birthday test will quickly find this problem.

...wyrand?

An excellent generator if you are OK with slightly lower quality. Because its output function (unlike splitmix) is not a bijection, some outputs are more likely to appear than others. You can easily observe this non-uniformity with a birthday test.

...romu?

Very fast, but relatively new and untested. Also, no guarantees about the period length.

Status

pgregory.net/rand is alpha software. API breakage and bugs should be expected before 1.0.

License

pgregory.net/rand is licensed under the Mozilla Public License Version 2.0.

Documentation

Overview

Package rand implements pseudo-random number generators unsuitable for security-sensitive work.

This package is considerably faster and generates higher quality random than the math/rand package. However, this package's outputs might be predictable regardless of how it's seeded. For random numbers suitable for security-sensitive work, see the crypto/rand package.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Shuffle added in v0.1.4

func Shuffle[S ~[]E, E any](r *Rand, s S)

Shuffle pseudo-randomizes the order of the elements of s.

Types

type Rand

type Rand struct {
	// contains filtered or unexported fields
}

Rand is a pseudo-random number generator based on the SFC64 algorithm by Chris Doty-Humphrey.

SFC64 has 256 bits of state, average period of ~2^255 and minimum period of at least 2^64. Generators returned by New (with empty or distinct seeds) are guaranteed to not run into each other for at least 2^64 iterations.

func New

func New(seed ...uint64) *Rand

New returns an initialized generator. If seed is empty, generator is initialized to a non-deterministic state. Otherwise, generator is seeded with the values from seed. New panics if len(seed) > 3.

func (*Rand) ExpFloat64

func (r *Rand) ExpFloat64() float64

ExpFloat64 returns an exponentially distributed float64 in the range (0, +math.MaxFloat64] with an exponential distribution whose rate parameter (lambda) is 1 and whose mean is 1/lambda (1). To produce a distribution with a different rate parameter, callers can adjust the output using:

sample = ExpFloat64() / desiredRateParameter

func (*Rand) Float32

func (r *Rand) Float32() float32

Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0, 1.0).

func (*Rand) Float64

func (r *Rand) Float64() float64

Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0, 1.0).

func (*Rand) Int

func (r *Rand) Int() int

Int returns a non-negative pseudo-random int.

func (*Rand) Int31

func (r *Rand) Int31() int32

Int31 returns a non-negative pseudo-random 31-bit integer as an int32.

func (*Rand) Int31n

func (r *Rand) Int31n(n int32) int32

Int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0, n). It panics if n <= 0.

func (*Rand) Int63

func (r *Rand) Int63() int64

Int63 returns a non-negative pseudo-random 63-bit integer as an int64.

func (*Rand) Int63n

func (r *Rand) Int63n(n int64) int64

Int63n returns, as an int64, a non-negative pseudo-random number in the half-open interval [0, n). It panics if n <= 0.

func (*Rand) Intn

func (r *Rand) Intn(n int) int

Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0, n). It panics if n <= 0.

func (*Rand) MarshalBinary

func (r *Rand) MarshalBinary() ([]byte, error)

MarshalBinary returns the binary representation of the current state of the generator.

func (*Rand) NormFloat64

func (r *Rand) NormFloat64() float64

NormFloat64 returns a normally distributed float64 in the range -math.MaxFloat64 through +math.MaxFloat64 inclusive, with standard normal distribution (mean = 0, stddev = 1). To produce a different normal distribution, callers can adjust the output using:

sample = NormFloat64() * desiredStdDev + desiredMean

func (*Rand) Perm

func (r *Rand) Perm(n int) []int

Perm returns, as a slice of n ints, a pseudo-random permutation of the integers in the half-open interval [0, n).

func (*Rand) Read

func (r *Rand) Read(p []byte) (n int, err error)

Read generates len(p) random bytes and writes them into p. It always returns len(p) and a nil error.

func (*Rand) Seed

func (r *Rand) Seed(seed uint64)

Seed uses the provided seed value to initialize the generator to a deterministic state.

func (*Rand) Shuffle

func (r *Rand) Shuffle(n int, swap func(i, j int))

Shuffle pseudo-randomizes the order of elements. n is the number of elements. Shuffle panics if n < 0. swap swaps the elements with indexes i and j.

For shuffling elements of a slice, prefer the top-level Shuffle function.

func (*Rand) Uint32

func (r *Rand) Uint32() uint32

Uint32 returns a pseudo-random 32-bit value as an uint32.

func (*Rand) Uint32n

func (r *Rand) Uint32n(n uint32) uint32

Uint32n returns, as an uint32, a pseudo-random number in [0, n). Uint32n(0) returns 0.

func (*Rand) Uint64

func (r *Rand) Uint64() uint64

Uint64 returns a pseudo-random 64-bit value as a uint64.

func (*Rand) Uint64n

func (r *Rand) Uint64n(n uint64) uint64

Uint64n returns, as an uint64, a pseudo-random number in [0, n). Uint64n(0) returns 0.

func (*Rand) UnmarshalBinary

func (r *Rand) UnmarshalBinary(data []byte) error

UnmarshalBinary sets the state of the generator to the state represented in data.

type Zipf

type Zipf struct {
	// contains filtered or unexported fields
}

A Zipf generates Zipf distributed variates.

func NewZipf

func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf

NewZipf returns a Zipf variate generator. The generator generates values k ∈ [0, imax] such that P(k) is proportional to (v + k) ** (-s). Requirements: s > 1 and v >= 1.

func (*Zipf) Uint64

func (z *Zipf) Uint64() uint64

Uint64 returns a value drawn from the Zipf distribution described by the Zipf object.

Directories

Path Synopsis
misc

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL