From f36b4fabd671cf739bd4afa07ff5409504f6f23c Mon Sep 17 00:00:00 2001 From: charlie <3140647@qq.com> Date: Tue, 7 Jun 2022 10:56:52 +0800 Subject: [PATCH] rand --- rand/fast_rand_generator.go | 78 +++++++++++++++++++++++++ rand/rand.go | 23 +++----- rand/rand_generator.go | 107 +++++++++++++++++++++------------- rand/rand_generator_test.go | 42 +++++++++++++ rand/rand_test.go | 99 +++++++++++++++++-------------- rand/secure_rand_generator.go | 49 ++++++++++++++++ rand/secure_rand_test.go | 17 ++++++ 7 files changed, 315 insertions(+), 100 deletions(-) create mode 100644 rand/fast_rand_generator.go create mode 100644 rand/rand_generator_test.go create mode 100644 rand/secure_rand_generator.go create mode 100644 rand/secure_rand_test.go diff --git a/rand/fast_rand_generator.go b/rand/fast_rand_generator.go new file mode 100644 index 0000000..1573255 --- /dev/null +++ b/rand/fast_rand_generator.go @@ -0,0 +1,78 @@ +package rand + +import ( + _ "unsafe" +) + +var fastGenerator = &fastRandGenerator{} + +type fastRandGenerator struct { +} + +func NewFastRandGenerator() *fastRandGenerator { + return fastGenerator +} + +func (*fastRandGenerator) Int63() int64 { + uu := uint64(uint64(Fastrand())<<32) ^ uint64(Fastrand()) + return int64(uu << 1 >> 1) +} + +func (r *fastRandGenerator) Int63n(n int64) int64 { + if n <= 0 { + panic("invalid argument to Int63n") + } + + if n&(n-1) == 0 { // n is power of two, can mask + return r.Int63() & (n - 1) + } + max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) + v := r.Int63() + for v > max { + v = r.Int63() + } + + return v % n +} + +func (*fastRandGenerator) Int31() int32 { + u := Fastrand() + return int32(u << 1 >> 1) +} + +func (r fastRandGenerator) Int31n(n int32) int32 { + if n <= 0 { + panic("invalid argument to Int31n") + } + + if n&(n-1) == 0 { // n is power of two, can mask + return r.Int31() & (n - 1) + } + + max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) + v := r.Int31() + for v > max { + v = r.Int31() + } + + return v % n +} + +func (*fastRandGenerator) Int() int { + u := Fastrand() + return int(u << 1 >> 1) +} + +func (r *fastRandGenerator) Intn(n int) int { + if n <= 0 { + panic("invalid argument to Intn") + } + if n <= 1<<31-1 { + return int(r.Int31n(int32(n))) + } + + return int(r.Int63n(int64(n))) +} + +//go:linkname Fastrand runtime.fastrand +func Fastrand() uint32 diff --git a/rand/rand.go b/rand/rand.go index 61587df..5a451a6 100644 --- a/rand/rand.go +++ b/rand/rand.go @@ -3,8 +3,7 @@ package rand import ( "crypto/rand" "io" - - "math/big" + _ "unsafe" "github.com/charlienet/go-mixed/bytesconv" ) @@ -20,7 +19,7 @@ const ( _ = allChars + "/+" ) -var rng = NewRandGenerator() +var rng = NewFastRandGenerator() // NewRandGenerator() type charScope struct { bytes []byte @@ -82,7 +81,11 @@ func (scope *charScope) Generate(length int) string { } type scopeConstraint interface { - ~int | ~int32 | ~int64 + ~int | ~int32 | ~int64 | ~uint32 +} + +func Int[T scopeConstraint]() T { + return T(rng.Int31()) } // 生成区间 n >= 0, n < max @@ -97,18 +100,6 @@ func IntRange[T scopeConstraint](min, max T) T { return T(n + min) } -func CryptoRange[T scopeConstraint](min, max T) T { - n := CryptoIntn(max - min) - return min + n -} - -func CryptoIntn[T ~int | ~int32 | ~int64](max T) T { - b := big.NewInt(int64(max)) - n, _ := rand.Int(rand.Reader, b) - - return T(n.Int64()) -} - func RandBytes(len int) ([]byte, error) { r := make([]byte, len) _, err := io.ReadFull(rand.Reader, r) diff --git a/rand/rand_generator.go b/rand/rand_generator.go index 2544bc7..3c870f2 100644 --- a/rand/rand_generator.go +++ b/rand/rand_generator.go @@ -2,24 +2,58 @@ package rand import ( mrnd "math/rand" - "runtime" - "sync/atomic" "time" + + "github.com/charlienet/go-mixed/locker" ) +// 随机数生成器接口 +type RandGenerator interface { + Int() int + Intn(int) int + Int31() int32 + Int31n(int32) int32 + Int63() int64 + Int63n(int64) int64 +} + +type rangeGenerator struct { + g RandGenerator +} + +func NewRangeGenerator(g RandGenerator) *rangeGenerator { + return &rangeGenerator{g: g} +} + +func (g rangeGenerator) Intr(min, max int) int { + n := max - min + return min + g.g.Intn(n) +} + +func (g rangeGenerator) Int31r(min, max int32) int32 { + n := max - min + return min + g.g.Int31n(n) +} + +func (g rangeGenerator) Int63r(min, max int64) int64 { + n := max - min + return min + g.g.Int63n(n) +} + var ( seed = time.Now().UnixNano() // 随机数种子 souce = mrnd.NewSource(time.Now().UnixNano()) // 用于初始化的随机数生成器 ) -type randGenerator struct { +type mathRandGenerator struct { source mrnd.Source - r uint32 + r locker.Locker } -func NewRandGenerator() *randGenerator { - return &randGenerator{ +func NewRandGenerator() *mathRandGenerator { + return &mathRandGenerator{ source: mrnd.NewSource(getSeed()), + r: locker.NewSpinLocker(), } } @@ -28,29 +62,11 @@ func getSeed() int64 { return seed } -// Int63n returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n <= 0. -func (r *randGenerator) Int63n(n int64) int64 { - if n <= 0 { - panic("invalid argument to Int63n") - } - if n&(n-1) == 0 { // n is power of two, can mask - return r.Int63() & (n - 1) - } - max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) - v := r.Int63() - for v > max { - v = r.Int63() - } - - return v % n -} - -func (r *randGenerator) Int31() int32 { +func (r *mathRandGenerator) Int31() int32 { return int32(r.Int63() >> 32) } -func (r *randGenerator) Int31n(n int32) int32 { +func (r *mathRandGenerator) Int31n(n int32) int32 { if n <= 0 { panic("invalid argument to Int31n") } @@ -66,37 +82,46 @@ func (r *randGenerator) Int31n(n int32) int32 { return v % n } -func (r *randGenerator) Int() int { +func (r *mathRandGenerator) Int() int { u := uint(r.Int63()) return int(u << 1 >> 1) // clear sign bit if int == int32 } // Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). // It panics if n <= 0. -func (r *randGenerator) Intn(n int) int { +func (r *mathRandGenerator) Intn(n int) int { if n <= 0 { panic("invalid argument to Intn") } if n <= 1<<31-1 { return int(r.Int31n(int32(n))) } + return int(r.Int63n(int64(n))) } -func (r *randGenerator) Int63() int64 { - r.lock() +// Int63n returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *mathRandGenerator) Int63n(n int64) int64 { + if n <= 0 { + panic("invalid argument to Int63n") + } + if n&(n-1) == 0 { // n is power of two, can mask + return r.Int63() & (n - 1) + } + max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) + v := r.Int63() + for v > max { + v = r.Int63() + } + + return v % n +} + +func (r *mathRandGenerator) Int63() int64 { + r.r.Lock() i := r.source.Int63() - r.unlock() + r.r.Unlock() return i } - -func (g *randGenerator) lock() { - for !atomic.CompareAndSwapUint32(&g.r, 0, 1) { - runtime.Gosched() - } -} - -func (g *randGenerator) unlock() { - atomic.StoreUint32(&g.r, 0) -} diff --git a/rand/rand_generator_test.go b/rand/rand_generator_test.go new file mode 100644 index 0000000..13015fd --- /dev/null +++ b/rand/rand_generator_test.go @@ -0,0 +1,42 @@ +package rand + +import "testing" + +func TestFastGenerate(t *testing.T) { + g := NewFastRandGenerator() + for i := 0; i < 100; i++ { + t.Log(g.Int63()) + } +} + +func BenchmarkGenerate(b *testing.B) { + b.Run("fast", func(b *testing.B) { + g1 := NewFastRandGenerator() + for i := 0; i < b.N; i++ { + g1.Int31() + } + }) + + b.Run("normal", func(b *testing.B) { + g1 := NewRandGenerator() + for i := 0; i < b.N; i++ { + g1.Int31() + } + }) +} + +func BenchmarkParallel(b *testing.B) { + g1 := NewFastRandGenerator() + b.RunParallel(func(p *testing.PB) { + for p.Next() { + g1.Int31() + } + }) + + g2 := NewRandGenerator() + b.RunParallel(func(p *testing.PB) { + for p.Next() { + g2.Int() + } + }) +} diff --git a/rand/rand_test.go b/rand/rand_test.go index 111a63f..e82862b 100644 --- a/rand/rand_test.go +++ b/rand/rand_test.go @@ -2,20 +2,21 @@ package rand_test import ( "bytes" + "fmt" "testing" - "time" - - mrnd "math/rand" "github.com/charlienet/go-mixed/rand" + "github.com/stretchr/testify/assert" ) +var generators = []rand.RandGenerator{ + rand.NewFastRandGenerator(), + rand.NewRandGenerator(), + rand.NewSecureRandGenerator(), +} + func TestRandString(t *testing.T) { t.Log(rand.AllChars.Generate(20)) - - // b, err := rand.RandBytes(32) - // t.Log(err) - // t.Log(hex.EncodeToString(b)) } func TestRandHex(t *testing.T) { @@ -23,14 +24,6 @@ func TestRandHex(t *testing.T) { t.Log(h) } -func TestRandMax(t *testing.T) { - mrnd.Seed(time.Now().UnixNano()) -} - -func TestCryptRand(t *testing.T) { - t.Log(rand.CryptoIntn(56545)) -} - func TestGenericsInterger(t *testing.T) { var max int32 = 55 @@ -56,6 +49,29 @@ func TestRange(t *testing.T) { } } +func TestFastrand(t *testing.T) { + t.Log(int(^uint(0) >> 1)) + + for _, g := range generators { + var max32 int32 = 1000 + for i := 0; i < 100000; i++ { + assert.GreaterOrEqual(t, max32, g.Int31n(max32)) + } + + var max64 int64 = 1000 + for i := 0; i < 100000; i++ { + assert.GreaterOrEqual(t, max64, g.Int63n(max64)) + } + + var max int = 1000 + for i := 0; i < 100000; i++ { + assert.GreaterOrEqual(t, max, g.Intn(max)) + } + + t.Logf("generator:%T pass", g) + } +} + func TestGenerator(t *testing.T) { g := rand.NewRandGenerator() @@ -77,6 +93,31 @@ func TestMutiGenerator(t *testing.T) { } } +func BenchmarkGenerator(b *testing.B) { + for _, g := range generators { + b.Run(fmt.Sprintf("%T", g), func(b *testing.B) { + for i := 0; i < b.N; i++ { + g.Int31() + } + }) + } +} + +func BenchmarkParallelGenerator(b *testing.B) { + for _, r := range generators { + b.Run(fmt.Sprintf("%T", r), func(b *testing.B) { + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + r.Int31() + } + }) + }) + + } +} + func BenchmarkParallel(b *testing.B) { rand.Hex.Generate(16) @@ -107,27 +148,6 @@ func BenchmarkHexParallel(b *testing.B) { }) } -func BenchmarkGenerator(b *testing.B) { - r := rand.NewRandGenerator() - for i := 0; i < b.N; i++ { - r.Int63() - } -} - -func BenchmarkParallelGenerator(b *testing.B) { - r := rand.NewRandGenerator() - for i:=0; i<10; i++{ - go r.Int63() - } - - b.ResetTimer() - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - r.Int63() - } - }) -} - func BenchmarkRandString(b *testing.B) { b.Log(rand.Hex.Generate(16)) b.ResetTimer() @@ -193,11 +213,4 @@ func BenchmarkRand(b *testing.B) { rand.Intn(100) } }) - - b.Run("crypto/rand", func(b *testing.B) { - for i := 0; i < b.N; i++ { - rand.CryptoIntn(100) - } - }) - } diff --git a/rand/secure_rand_generator.go b/rand/secure_rand_generator.go new file mode 100644 index 0000000..3efbdef --- /dev/null +++ b/rand/secure_rand_generator.go @@ -0,0 +1,49 @@ +package rand + +import ( + "crypto/rand" + "math/big" + + "github.com/charlienet/go-mixed/bytesconv" +) + +type secureRandGenerator struct{} + +func NewSecureRandGenerator() secureRandGenerator { + return secureRandGenerator{} +} + +func (secureRandGenerator) Int() int { + i, _ := bytesconv.LittleEndian.BytesToUInt64(read(4)) + return int(i << 1 >> 1) +} + +func (s secureRandGenerator) Intn(max int) int { + return int(s.Int63n(int64(max))) +} + +func (secureRandGenerator) Int31() int32 { + i, _ := bytesconv.LittleEndian.BytesToUInt64(read(4)) + return int32(uint32(i) << 1 >> 1) +} + +func (s secureRandGenerator) Int31n(max int32) int32 { + return int32(s.Int63n(int64(max))) +} + +func (secureRandGenerator) Int63() int64 { + i, _ := bytesconv.LittleEndian.BytesToUInt64(read(8)) + return int64(i << 1 >> 1) +} + +func (secureRandGenerator) Int63n(max int64) int64 { + n, _ := rand.Int(rand.Reader, big.NewInt(max)) + return n.Int64() +} + +func read(n int) []byte { + buf := make([]byte, 8) + rand.Read(buf) + + return buf +} diff --git a/rand/secure_rand_test.go b/rand/secure_rand_test.go new file mode 100644 index 0000000..c9f3fe3 --- /dev/null +++ b/rand/secure_rand_test.go @@ -0,0 +1,17 @@ +package rand + +import "testing" + +func TestSecureInt63n(t *testing.T) { + r := NewSecureRandGenerator() + for i := 0; i < 100; i++ { + t.Log(r.Int63n(100000)) + } +} + +func TestSecureInt63(t *testing.T) { + r := NewSecureRandGenerator() + for i := 0; i < 100; i++ { + t.Log(r.Int63()) + } +}