Skip to content

Commit

Permalink
Performance improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
thorstenrie committed Feb 20, 2023
1 parent c8ed9fd commit 1be8f17
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 83 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Each interface function returns a [rnd.Rand](https://pkg.go.dev/math/rand#Rand)
- Cryptographically secure random number generator based on [crypto/rand](https://pkg.go.dev/crypto/rand)
- Pseudo-random number generator based on [math/rand](https://pkg.go.dev/math/rand)
- Deterministic pseudo-random number generator based on [math/rand](https://pkg.go.dev/math/rand)
- A custom implementation of a random number generator [Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#Source) with [New](https://pkg.go.dev/github.com/thorstenrie/tsrand#New)
- A custom implementation of a random number generator [Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#Source) with [New](https://pkg.go.dev/github.com/thorstenrie/tsrand#New). It is the responsibility of the source to be safe for concurrent use by multiple goroutines.
- Example of a very simple pseudo-random number generator [SimpleSource](https://pkg.go.dev/github.com/thorstenrie/tsrand#SimpleSource) based on an very simple example from [Wikipedia](https://en.wikipedia.org/wiki/Pseudorandom_number_generator#Implementation)
- Example pseudo-random number generator [MT32Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#MT32Source) based on the [32-bit Mersenne Twister](http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/MT2002/emt19937ar.html)
- Example pseudo-random number generator [MT64Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#MT64Source) based on the [64-bit Mersenne Twister](http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/emt64.html)
Expand All @@ -56,11 +56,11 @@ Results from linux, amd64, AMD Ryzen 5 2600X Six-Core Processor

| Source | Benchmark |
|---|---|
| [crypto/rand](https://pkg.go.dev/crypto/rand) | 465 ns/op |
| [math/rand](https://pkg.go.dev/math/rand) | 15 ns/op |
| [SimpleSource](https://pkg.go.dev/github.com/thorstenrie/tsrand#SimpleSource) | 40 ns/op |
| [MT32Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#MT32Source) | 33 ns/op |
| [MT64Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#MT64Source) | 17 ns/op |
| [crypto/rand](https://pkg.go.dev/crypto/rand) | ~500 ns/op |
| [math/rand](https://pkg.go.dev/math/rand) | ~7 ns/op |
| [SimpleSource](https://pkg.go.dev/github.com/thorstenrie/tsrand#SimpleSource) | ~35 ns/op |
| [MT32Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#MT32Source) | ~12 ns/op |
| [MT64Source](https://pkg.go.dev/github.com/thorstenrie/tsrand#MT64Source) | ~6 ns/op |

## Example

Expand Down
4 changes: 2 additions & 2 deletions rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ func NewCryptoRand() (*rand.Rand, error) {
}

// NewPseudoRandomRand returns a new instance of rand.Rand which provides a pseudo-
// random number generator based on math/rand. It is safe for concurrent use by multiple goroutines.
// random number generator based on math/rand. It is not safe for concurrent use by multiple goroutines.
// The random number generator is initialized with time.Now().UnixNano(). The output might be easily
// predictable and is unsuitable for security-sensitive services.
func NewPseudoRandomRand() (*rand.Rand, error) {
return New(newDeterministicSource(time.Now().UnixNano()))
}

// NewDeterministicRand returns a new instance of rand.Rand which provides a deterministic pseudo-
// random number generator based on math/rand. It is safe for concurrent use by multiple goroutines.
// random number generator based on math/rand. It is not safe for concurrent use by multiple goroutines.
// It is initialized with defaultSeed = 1 and returns a deterministic random sequence. The output is
// easily predictable and is unsuitable for security-sensitive services.
func NewDeterministicRand() (*rand.Rand, error) {
Expand Down
19 changes: 2 additions & 17 deletions source_math.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,19 @@ package tsrand
// Import standard library packages
import (
mrand "math/rand" // math/rand
"sync" // sync
)

// dSource implements Source64, uses the math/rand pseudo-random number generator and can be used as source for a rand.Rand.
// It holds the deterministic pseudo-random number generator drnd and a sync.Mutex
// to enable concurrent use of an instance of a dSource. A dSource is safe for
// It holds the deterministic pseudo-random number generator drnd. A dSource is not safe for
// concurrent use by multiple goroutines. The output might be easily predictable and is unsuitable
// for security-sensitive services.
type dSource struct {
mu sync.Mutex // mutex to enable concurrency
drnd *mrand.Rand // deterministic pseudo-random number generator
}

// NewDeterministicSource returns a new instance of dSource. Source implements Source64,
// uses the math/rand pseudo-random number generator and can be used as source for a rand.Rand.
// A dSource is safe for concurrent use by multiple goroutines. The output might be
// A dSource is not safe for concurrent use by multiple goroutines. The output might be
// easily predictable and is unsuitable for security-sensitive services. The deterministic
// pseud-random number generator is initiatlized with seed.
func newDeterministicSource(seed int64) *dSource {
Expand All @@ -36,34 +33,22 @@ func newDeterministicSource(seed int64) *dSource {
// Seed initializes the pseudo-random number generator source to
// a deterministic state defined by s.
func (m *dSource) Seed(s int64) {
// Lock the source
m.mu.Lock()
// Set seed pf drnd to s
m.drnd.Seed(s)
// Unlock the source
m.mu.Unlock()
}

// Uint64 returns a pseudo-random 64-bit value.
func (m *dSource) Uint64() uint64 {
// Lock the source
m.mu.Lock()
// Retrieve pseudo-random uint64 from drnd in v
v := m.drnd.Uint64()
// Unlock the source
m.mu.Unlock()
// Return v
return v
}

// Int63 returns a random 63-bit integer
func (m *dSource) Int63() int64 {
// Lock the source
m.mu.Lock()
// Retrieve pseudo-random 64-bit integer from drnd in v
v := m.drnd.Int63()
// Unlock the source
m.mu.Unlock()
// Return v
return v
}
Expand Down
31 changes: 9 additions & 22 deletions source_mt32.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
// that can be found in the LICENSE file.
package tsrand

// Import standard library package sync
import "sync" // sync

// MT32Source implements Source64 and can be used as source for a rand.Rand. It is based on the
// reference implementation of the 32-bit Mersenne Twister.
// MT32Source holds the pseudo-random number generator internal states mt and mti and a sync.Mutex
// to enable concurrent use of an instance of a MT32Source. A MT32Source is safe for
// MT32Source holds the pseudo-random number generator internal states mt and mti. A MT32Source is not safe for
// concurrent use by multiple goroutines. The output might be easily predictable and is unsuitable
// for security-sensitive services.
type MT32Source struct {
mt []uint32 // slice for the state vector
mti int // index and mti==N+1 means mt is not initialized
mu sync.Mutex // mutex to enable concurrency
mt []uint32 // slice for the state vector
mti int // index and mti==N+1 means mt is not initialized
}

// Period parameters based on the reference implementation of the 32-bit Mersenne Twister
Expand All @@ -35,29 +30,25 @@ var (

// NewMT32Source returns a new instance of MT32Source. MT32Source implements Source64,
// is based on the reference implementation of the 32-bit Mersenne Twister and can be used as source for a rand.Rand.
// A MT32Source is safe for concurrent use by multiple goroutines. The output might be
// A MT32Source is not safe for concurrent use by multiple goroutines. The output might be
// easily predictable and is unsuitable for security-sensitive services.
func NewMT32Source() *MT32Source {
src := &MT32Source{mt: make([]uint32, mt32c.n), mti: mt32c.n + 1}
return src
}

// seedUnsafe initializes the state vector with seed s. It is not safe for concurrent use.
func (src *MT32Source) seedUnsafe(s int64) {
// seed initializes the state vector with seed s.
func (src *MT32Source) seed(s int64) {
src.mt[0] = uint32(s & 0xffffffff)
for src.mti = 1; src.mti < mt32c.n; src.mti++ {
src.mt[src.mti] = (uint32(1812433253)*(src.mt[src.mti-1]^(src.mt[src.mti-1]>>30)) + uint32(src.mti))
}
}

// Seed initializes the state vector with seed s. It is safe for concurrent use.
// Seed initializes the state vector with seed s.
func (src *MT32Source) Seed(s int64) {
// Lock source
src.mu.Lock()
// Initialization of the state vector with seed s
src.seedUnsafe(s)
// Unlock source
src.mu.Unlock()
src.seed(s)
}

// uint32 returns a pseudo-random 32-bit value. The implementation is
Expand All @@ -67,13 +58,11 @@ func (src *MT32Source) uint32() uint32 {
y uint32
mag01 [2]uint32 = [2]uint32{0, mt32c.matrixA}
)
// Lock source
src.mu.Lock()
if src.mti >= mt32c.n {
var kk int
// Initialize state vector with default seed if not initialized with a seed before
if src.mti == mt32c.n+1 {
src.seedUnsafe(int64(mt32c.defaultSeed))
src.seed(int64(mt32c.defaultSeed))
}
for kk = 0; kk < mt32c.n-mt32c.m; kk++ {
y = (src.mt[kk] & mt32c.uMask) | (src.mt[kk+1] & mt32c.lMask)
Expand All @@ -89,8 +78,6 @@ func (src *MT32Source) uint32() uint32 {
}
y = src.mt[src.mti]
src.mti += 1
// Unlock source
src.mu.Unlock()
// Tempering
y ^= (y >> 11)
y ^= (y << 7) & 0x9d2c5680
Expand Down
31 changes: 9 additions & 22 deletions source_mt64.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
// that can be found in the LICENSE file.
package tsrand

// Import standard library package sync
import "sync" // sync

// MT64Source implements Source64 and can be used as source for a rand.Rand. It is based on the
// reference implementation of the 64-bit Mersenne Twister.
// MT64Source holds the pseudo-random number generator internal states mt and mti and a sync.Mutex
// to enable concurrent use of an instance of a MT64Source. A MT64Source is safe for
// MT64Source holds the pseudo-random number generator internal states mt and mti. A MT64Source is not safe for
// concurrent use by multiple goroutines. The output might be easily predictable and is unsuitable
// for security-sensitive services.
type MT64Source struct {
mt []uint64 // slice for the state vector
mti int // index and mti==N+1 means mt is not initialized
mu sync.Mutex // mutex to enable concurrency
mt []uint64 // slice for the state vector
mti int // index and mti==N+1 means mt is not initialized
}

// Period parameters based on the reference implementation of the 64-bit Mersenne Twister
Expand All @@ -35,29 +30,25 @@ var (

// NewMT64Source returns a new instance of MT64Source. MT64Source implements Source64,
// is based on the reference implementation of the 64-bit Mersenne Twister and can be used as source for a rand.Rand.
// A MT64Source is safe for concurrent use by multiple goroutines. The output might be
// A MT64Source is not safe for concurrent use by multiple goroutines. The output might be
// easily predictable and is unsuitable for security-sensitive services.
func NewMT64Source() *MT64Source {
src := &MT64Source{mt: make([]uint64, mt64c.n), mti: mt64c.n + 1}
return src
}

// seedUnsafe initializes the state vector with seed s. It is not safe for concurrent use.
func (src *MT64Source) seedUnsafe(s int64) {
// seed initializes the state vector with seed s.
func (src *MT64Source) seed(s int64) {
src.mt[0] = uint64(s)
for src.mti = 1; src.mti < mt64c.n; src.mti++ {
src.mt[src.mti] = (uint64(6364136223846793005)*(src.mt[src.mti-1]^(src.mt[src.mti-1]>>62)) + uint64(src.mti))
}
}

// Seed initializes the state vector with seed s. It is safe for concurrent use.
// Seed initializes the state vector with seed s.
func (src *MT64Source) Seed(s int64) {
// Lock source
src.mu.Lock()
// Initialization of the state vector with seed s
src.seedUnsafe(s)
// Unlock source
src.mu.Unlock()
src.seed(s)
}

// Uint64 returns a pseudo-random 64-bit value. The implementation is
Expand All @@ -67,13 +58,11 @@ func (src *MT64Source) Uint64() uint64 {
x uint64
mag01 [2]uint64 = [2]uint64{0, mt64c.matrixA}
)
// Lock source
src.mu.Lock()
if src.mti >= mt64c.n {
var i int
// Initialize state vector with default seed if not initialized with a seed before
if src.mti == mt64c.n+1 {
src.seedUnsafe(int64(mt64c.defaultSeed))
src.seed(int64(mt64c.defaultSeed))
}
for i = 0; i < mt64c.n-mt64c.m; i++ {
x = (src.mt[i] & mt64c.uMask) | (src.mt[i+1] & mt64c.lMask)
Expand All @@ -89,8 +78,6 @@ func (src *MT64Source) Uint64() uint64 {
}
x = src.mt[src.mti]
src.mti += 1
// Unlock source
src.mu.Unlock()
// Tempering
x ^= (x >> 29) & 0x5555555555555555
x ^= (x << 17) & 0x71D67FFFEDA60000
Expand Down
17 changes: 3 additions & 14 deletions source_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@
// that can be found in the LICENSE file.
package tsrand

// Import standard library packages
// Import standard library packages and lpstats
import (
"math" // math
"sync" // sync

"github.com/thorstenrie/lpstats" // lpstats
)

// SimpleSource implements Source64 and can be used as source for a rand.Rand.
// It holds the seed s which can be initialized with Seed(int64) and a sync.Mutex
// to enable concurrent use of an instance of a SimpleSource. A SimpleSource is safe for
// It holds the seed s which can be initialized with Seed(int64). A SimpleSource is not safe for
// concurrent use by multiple goroutines. The output might be easily predictable and is unsuitable
// for security-sensitive services.
type SimpleSource struct {
s int64 // seed
mu sync.Mutex // mutex to enable concurrency
s int64 // seed
}

// NewSimpleSource returns a pointer to a new SimpleSource initiatlized with the default seed.
Expand All @@ -30,12 +27,8 @@ func NewSimpleSource() *SimpleSource {
// Seed initializes the pseudo-random number generator source to
// a deterministic state defined by s.
func (ex *SimpleSource) Seed(s int64) {
// Lock source
ex.mu.Lock()
// Set seed
ex.s = s
// Unlock source
ex.mu.Unlock()
}

// Uint64 returns a pseudo-random 64-bit value. The pseudo-random value
Expand All @@ -47,8 +40,6 @@ func (ex *SimpleSource) Uint64() uint64 {
// Int63 returns a pseudo-random 63-bit integer determined by a
// deterministic calculation based on the seed.
func (ex *SimpleSource) Int63() int64 {
// Lock source
ex.mu.Lock()
// Generate pseudo random Int63
var (
// Save sign of current seed
Expand All @@ -67,8 +58,6 @@ func (ex *SimpleSource) Int63() int64 {
)
// Increment seed
ex.s++
// Unlock source
ex.mu.Unlock()
// Return Int63
return vi
}
Expand Down

0 comments on commit 1be8f17

Please sign in to comment.