Compare commits

...

2 Commits

2 changed files with 42 additions and 22 deletions

@ -4,8 +4,11 @@ import (
crip "crypto/rand" crip "crypto/rand"
"encoding/binary" "encoding/binary"
"math/rand" "math/rand"
"os"
"reflect"
"sync" "sync"
"time" "time"
"unsafe"
"nullprogram.com/x/rng" "nullprogram.com/x/rng"
@ -16,11 +19,11 @@ type randPool struct {
sync.Pool sync.Pool
} }
func (p *randPool) Get() *rand.Rand { func (p *randPool) Get() *rng.SplitMix64 {
return p.Pool.Get().(*rand.Rand) return p.Pool.Get().(*rng.SplitMix64)
} }
func (p *randPool) Put(r *rand.Rand) { func (p *randPool) Put(r *rng.SplitMix64) {
p.Pool.Put(r) p.Pool.Put(r)
} }
@ -30,8 +33,7 @@ var (
New: func() interface{} { New: func() interface{} {
sm64 := new(rng.SplitMix64) sm64 := new(rng.SplitMix64)
sm64.Seed(GetCryptoSeed()) sm64.Seed(GetCryptoSeed())
prng := rand.New(sm64) //nolint:gosec return sm64
return prng
}, },
}, },
} }
@ -42,16 +44,18 @@ var (
func setSharedRand() { func setSharedRand() {
hardLocc.Lock() hardLocc.Lock()
sharedRand = lolXD.Get() sharedRand = rand.New(lolXD.Get())
hardLocc.Unlock() hardLocc.Unlock()
} }
func AcquireRand() *rand.Rand { func AcquireRand() *rand.Rand {
return lolXD.Get() return rand.New(lolXD.Get())
} }
func ReleaseRand(r *rand.Rand) { func ReleaseRand(r *rand.Rand) {
lolXD.Put(r) srcField := reflect.ValueOf(r).Elem().FieldByName("src")
src := reflect.NewAt(srcField.Type(), unsafe.Pointer(srcField.UnsafeAddr())).Elem().Interface().(*rng.SplitMix64)
lolXD.Put(src)
r = nil r = nil
} }
@ -80,18 +84,17 @@ func GetOptimizedRand() *rand.Rand {
} }
// GetSharedRand returns a pointer to our shared optimized rand.Rand which uses crypto/rand to seed a splitmix64 rng. // GetSharedRand returns a pointer to our shared optimized rand.Rand which uses crypto/rand to seed a splitmix64 rng.
// WARNING - RACY - This is not thread safe, and should only be used in a single-threaded context.
func GetSharedRand() *rand.Rand { func GetSharedRand() *rand.Rand {
getSharedRand.Do(func() { getSharedRand.Do(func() {
setSharedRand() setSharedRand()
}) })
return sharedRand return rand.New(sharedRand)
} }
// RNGUint32 returns a random uint32 using crypto/rand and splitmix64. // RNGUint32 returns a random uint32 using crypto/rand and splitmix64.
func RNGUint32() uint32 { func RNGUint32() uint32 {
r := lolXD.Get() r := lolXD.Get()
ui := r.Uint32() ui := uint32(r.Int63() >> 31)
lolXD.Put(r) lolXD.Put(r)
return ui return ui
} }
@ -106,8 +109,25 @@ RNG returns integer with a maximum amount of 'n' using a global/shared instance
*/ */
func RNG(n int) int { func RNG(n int) int {
r := lolXD.Get() r := lolXD.Get()
i := r.Intn(n) defer lolXD.Put(r)
lolXD.Put(r) i := 0
if n <= 0 {
// because panic is just rude.
_, _ = os.Stderr.WriteString("RNG: n must be greater than 0, returning 0")
return i
}
if n <= 1<<31-1 {
n32 := int32(n)
if n32&(n32-1) == 0 {
i = int(int32(r.Int63()>>32) & (n32 - 1))
}
maximum := int32((1 << 31) - 1 - (1<<31)%uint32(n32))
v := int32(r.Int63() >> 32)
for v > maximum {
v = int32(r.Int63() >> 32)
}
i = int(v % n32)
}
return i return i
} }
@ -152,7 +172,7 @@ randStr is an overoptimized (read: dummy fast) random string generator.
at 55,555 characters, we are at: at 55,555 characters, we are at:
~57,000 bytes per op with string builders ~57,000 bytes per op with byte builders
vs vs
~210,000 bytes per op with string builders. ~210,000 bytes per op with string builders.
@ -167,7 +187,7 @@ func randStr(upper bool, size int) string {
buf := strBufs.Get() buf := strBufs.Get()
r := lolXD.Get() r := lolXD.Get()
for i := 0; i != size; i++ { for i := 0; i != size; i++ {
ui32 := int(r.Uint32()) ui32 := int(r.Int63() >> 31)
switch upper { switch upper {
case true: case true:
_ = buf.WriteByte(charsetWithUpper[ui32%len(charsetWithUpper)]) _ = buf.WriteByte(charsetWithUpper[ui32%len(charsetWithUpper)])

@ -49,15 +49,15 @@ func Test_RNG(t *testing.T) {
t.Errorf("GetSharedRand(55555) returned the same value twice!") t.Errorf("GetSharedRand(55555) returned the same value twice!")
} }
r := AcquireRand() r := AcquireRand()
one := r.Intn(55555) one := r.Uint64()
two := r.Intn(55555) two := r.Uint64()
if one == two { if one == two {
t.Errorf("AcquireRand() returned the same value twice!") t.Errorf("AcquireRand() returned the same value twice!")
} }
ReleaseRand(r) ReleaseRand(r)
r = AcquireRand() r = AcquireRand()
one1 := r.Intn(55555) one1 := r.Uint64()
two1 := r.Intn(55555) two1 := r.Uint64()
if one1 == two1 { if one1 == two1 {
t.Errorf("AcquireRand() returned the same value twice!") t.Errorf("AcquireRand() returned the same value twice!")
} }
@ -168,13 +168,13 @@ func Test_RNGUint32(t *testing.T) {
func Benchmark_RandStr(b *testing.B) { func Benchmark_RandStr(b *testing.B) {
toTest := []int{5, 25, 55, 500, 55555} toTest := []int{5, 25, 55, 500, 55555}
for n := range toTest { for _, ln := range toTest {
for i := 1; i != 5; i++ { for i := 1; i != 5; i++ {
b.Run(fmt.Sprintf("lenSeries%d/run%d", n, i), func(b *testing.B) { b.Run(fmt.Sprintf("len%d/run%d", ln, i), func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for tn := 0; tn != b.N; tn++ { for tn := 0; tn != b.N; tn++ {
RandStr(n) RandStr(ln)
} }
}) })
} }