add package guid; improve performance of package grand

This commit is contained in:
john
2020-05-16 21:56:31 +08:00
parent ebdc5d9c9d
commit caead810e2
11 changed files with 347 additions and 68 deletions

View File

@ -8,6 +8,6 @@ import (
func main() {
for i := 0; i < 100; i++ {
fmt.Println(grand.Rand(0, 99999))
fmt.Println(grand.S(16))
}
}

View File

@ -0,0 +1,13 @@
package main
import (
"fmt"
"github.com/gogf/gf/util/guid"
)
func main() {
for i := 0; i < 1000; i++ {
s := guid.S()
fmt.Println(s, len(s))
}
}

View File

@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/gogf/gf/util/guid"
)
func main() {
for i := 0; i < 10; i++ {
s := guid.New([]byte("123"))
fmt.Println(s, len(s))
}
fmt.Println()
for i := 0; i < 10; i++ {
s := guid.New([]byte("123"), []byte("456"))
fmt.Println(s, len(s))
}
fmt.Println()
for i := 0; i < 10; i++ {
s := guid.New([]byte("123"), []byte("456"), []byte("789"))
fmt.Println(s, len(s))
}
}

View File

@ -8,7 +8,7 @@ package gredis_test
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/guuid"
"github.com/gogf/gf/util/guid"
"testing"
"time"
@ -234,7 +234,7 @@ func Test_Bool(t *testing.T) {
func Test_Int(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
redis := gredis.New(config)
key := guuid.New()
key := guid.S()
defer redis.Do("DEL", key)
_, err := redis.Do("SET", key, 1)
@ -249,7 +249,7 @@ func Test_Int(t *testing.T) {
func Test_HSet(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
redis := gredis.New(config)
key := guuid.New()
key := guid.S()
defer redis.Do("DEL", key)
_, err := redis.Do("HSET", key, "name", "john")
@ -265,7 +265,7 @@ func Test_HGetAll(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var err error
redis := gredis.New(config)
key := guuid.New()
key := guid.S()
defer redis.Do("DEL", key)
_, err = redis.Do("HSET", key, "id", "100")

View File

@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package ghash provides some popular hash functions(uint32/uint64) in go.
// Package ghash provides some classic hash functions(uint32/uint64) in go.
package ghash
// BKDR Hash Function
@ -160,7 +160,7 @@ func DJBHash(str []byte) uint32 {
return hash
}
// DJB Hash Function 64
// DJB Hash Function 64.
func DJBHash64(str []byte) uint64 {
var hash uint64 = 5381
for i := 0; i < len(str); i++ {

View File

@ -19,14 +19,21 @@ var (
characters = letters + digits + symbols // 94
)
// Meet randomly calculate whether the given probability <num>/<total> is met.
func Meet(num, total int) bool {
return Intn(total) < num
}
// MeetProb randomly calculate whether the given probability is met.
func MeetProb(prob float32) bool {
return Intn(1e7) < int(prob*1e7)
// Intn returns a int number which is between 0 and max: [0, max).
//
// Note that:
// 1. The <max> can only be greater than 0, or else it returns <max> directly;
// 2. The result is greater than or equal to 0, but less than <max>;
// 3. The result number is 32bit and less than math.MaxUint32.
func Intn(max int) int {
if max <= 0 {
return max
}
n := int(binary.LittleEndian.Uint32(<-bufferChan)) % max
if (max > 0 && n < 0) || (max < 0 && n > 0) {
return -n
}
return n
}
// B retrieves and returns random bytes of given length <n>.
@ -73,12 +80,18 @@ func N(min, max int) int {
// The optional parameter <symbols> specifies whether the result could contain symbols,
// which is false in default.
func S(n int, symbols ...bool) string {
b := make([]byte, n)
if n <= 0 {
return ""
}
var (
b = make([]byte, n)
numberBytes = B(n)
)
for i := range b {
if len(symbols) > 0 && symbols[0] {
b[i] = characters[Intn(94)]
b[i] = characters[numberBytes[i]%94]
} else {
b[i] = characters[Intn(62)]
b[i] = characters[numberBytes[i]%62]
}
}
return *(*string)(unsafe.Pointer(&b))
@ -87,37 +100,67 @@ func S(n int, symbols ...bool) string {
// Str randomly picks and returns <n> count of chars from given string <s>.
// It also supports unicode string like Chinese/Russian/Japanese, etc.
func Str(s string, n int) string {
b := make([]rune, n)
runes := []rune(s)
for i := range b {
b[i] = runes[Intn(len(runes))]
if n <= 0 {
return ""
}
var (
b = make([]rune, n)
runes = []rune(s)
)
if len(runes) <= 255 {
numberBytes := B(n)
for i := range b {
b[i] = runes[int(numberBytes[i])%len(runes)]
}
} else {
for i := range b {
b[i] = runes[Intn(len(runes))]
}
}
return string(b)
}
// Digits returns a random string which contains only digits, and its length is <n>.
func Digits(n int) string {
b := make([]byte, n)
if n <= 0 {
return ""
}
var (
b = make([]byte, n)
numberBytes = B(n)
)
for i := range b {
b[i] = digits[Intn(10)]
b[i] = digits[numberBytes[i]%10]
}
return *(*string)(unsafe.Pointer(&b))
}
// Letters returns a random string which contains only letters, and its length is <n>.
func Letters(n int) string {
b := make([]byte, n)
if n <= 0 {
return ""
}
var (
b = make([]byte, n)
numberBytes = B(n)
)
for i := range b {
b[i] = letters[Intn(52)]
b[i] = letters[numberBytes[i]%52]
}
return *(*string)(unsafe.Pointer(&b))
}
// Symbols returns a random string which contains only symbols, and its length is <n>.
func Symbols(n int) string {
b := make([]byte, n)
if n <= 0 {
return ""
}
var (
b = make([]byte, n)
numberBytes = B(n)
)
for i := range b {
b[i] = symbols[Intn(52)]
b[i] = symbols[numberBytes[i]%32]
}
return *(*string)(unsafe.Pointer(&b))
}
@ -134,19 +177,12 @@ func Perm(n int) []int {
return m
}
// Intn returns a int number which is between 0 and max: [0, max).
//
// Note that:
// 1. The <max> can only be greater than 0, or else it returns <max> directly;
// 2. The result is greater than or equal to 0, but less than <max>;
// 3. The result number is 32bit and less than math.MaxUint32.
func Intn(max int) int {
if max <= 0 {
return max
}
n := int(binary.LittleEndian.Uint32(<-bufferChan)) % max
if (max > 0 && n < 0) || (max < 0 && n > 0) {
return -n
}
return n
// Meet randomly calculate whether the given probability <num>/<total> is met.
func Meet(num, total int) bool {
return Intn(total) < num
}
// MeetProb randomly calculate whether the given probability is met.
func MeetProb(prob float32) bool {
return Intn(1e7) < int(prob*1e7)
}

View File

@ -29,33 +29,13 @@ func init() {
// to produce the random bytes, and a buffer chan to store the random bytes.
// So it has high performance to generate random numbers.
func asyncProducingRandomBufferBytesLoop() {
var (
step = 0
buffer = make([]byte, 1024)
)
buffer := make([]byte, 1024)
for {
if n, err := rand.Read(buffer); err != nil {
panic(err)
} else {
for i := 0; i < n-4; {
for i := 0; i < n-4; i += 4 {
bufferChan <- buffer[i : i+4]
i++
}
// Reuse the rand buffer.
for i := 0; i < n; i++ {
step = int(buffer[0]) % 10
if step != 0 {
break
}
}
// The step cannot be 0,
// as it will produce the same random number as previous.
if step == 0 {
step = 2
}
for i := 0; i < n-4; {
bufferChan <- buffer[i : i+4]
i += step
}
}
}

View File

@ -16,7 +16,10 @@ import (
"github.com/gogf/gf/util/grand"
)
var buffer = make([]byte, 8)
var (
buffer = make([]byte, 8)
strForStr = "我爱GoFrame"
)
func Benchmark_Rand_Intn(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -48,18 +51,36 @@ func Benchmark_Rand_N2(b *testing.B) {
}
}
func Benchmark_Str(b *testing.B) {
func Benchmark_B(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.B(16)
}
}
func Benchmark_S(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.S(16)
}
}
func Benchmark_StrSymbols(b *testing.B) {
func Benchmark_S_Symbols(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.S(16, true)
}
}
func Benchmark_Str(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.Str(strForStr, 16)
}
}
func Benchmark_Symbols(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.Symbols(16)
}
}
func Benchmark_Uint32Converting(b *testing.B) {
for i := 0; i < b.N; i++ {
binary.LittleEndian.Uint32([]byte{1, 1, 1, 1})

134
util/guid/guid.go Normal file
View File

@ -0,0 +1,134 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package guid provides simple and high performance unique id generation functionality.
//
// PLEASE VERY NOTE:
// This package only provides unique number generation for simple, convenient and most common
// usage purpose, but does not provide strict global unique number generation. Please refer
// to UUID algorithm for global unique number generation if necessary.
package guid
import (
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/encoding/ghash"
"github.com/gogf/gf/net/gipv4"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
"os"
"strconv"
"time"
)
var (
sequence gtype.Uint32 // Sequence for unique purpose of current process.
macAddrStr string // MAC addresses hash result in 7 bytes.
processIdStr string // Process id in 4 bytes.
randomStrBase = "0123456789abcdefghijklmnopqrstuvwxyz" // 36
)
func init() {
// MAC addresses hash result in 7 bytes.
macs, _ := gipv4.MacArray()
if len(macs) > 0 {
var b []byte
for _, mac := range macs {
b = append(b, []byte(mac)...)
}
macAddrStr = strconv.FormatUint(uint64(ghash.DJBHash(b)), 36)
if n := 7 - len(macAddrStr); n > 0 {
for i := 0; i < n; i++ {
macAddrStr = "0" + macAddrStr
}
}
if n := len(macAddrStr) - 7; n > 0 {
macAddrStr = macAddrStr[n:]
}
}
// Process id in 4 bytes.
processIdStr = strconv.FormatInt(int64(os.Getpid()), 36)
if n := 4 - len(processIdStr); n > 0 {
for i := 0; i < n; i++ {
processIdStr = "0" + processIdStr
}
}
if n := len(processIdStr) - 4; n > 0 {
processIdStr = processIdStr[n:]
}
}
// S creates and returns an unique string in 36 bytes that meets most
// common usages without strict UUID algorithm.
// The returned string is composed with:
// MAC(7) + PID(4) + Sequence(7) + TimestampNano(12) + RandomString(6)
func S() string {
b := make([]byte, 36)
copy(b, macAddrStr)
copy(b[7:], processIdStr)
copy(b[11:], getSequence())
copy(b[18:], strconv.FormatInt(time.Now().UnixNano(), 36))
copy(b[30:], randomStr(randomStrBase, 6))
return gconv.UnsafeBytesToStr(b)
}
// New creates and returns an unique string in 36 bytes using custom data.
// The returned string is composed with:
// Data...(7 - 21) + TimestampNano(12) + RandomString(3 - 17)
//
// The specified <data> can be count of 1 to 3. No matter how long each of the <data>
// size is, each of them will be hashed into 7 bytes as part of the result. If given
// <data> count is less than 3, the leftover size of the result bytes will be token by
// random string.
//
// Note that the <data> can not be empty.
func New(data ...[]byte) string {
if len(data) == 0 {
panic("data cannot be empty")
}
if len(data) > 3 {
panic("data count too long, no more than 3")
}
b := make([]byte, 36)
n := 0
for i, v := range data {
copy(b[i*7:], getDataHashStr(v))
n += 7
}
copy(b[n:], strconv.FormatInt(time.Now().UnixNano(), 36))
copy(b[n+12:], randomStr(randomStrBase, 36-n-12))
return gconv.UnsafeBytesToStr(b)
}
// getSequence increases and returns the sequence string in 7 bytes.
func getSequence() string {
b := make([]byte, 7)
copy(b, []byte{'0', '0', '0', '0', '0', '0', '0'})
s := strconv.FormatUint(uint64(sequence.Add(1)), 36)
copy(b[7-len(s):], s)
return gconv.UnsafeBytesToStr(b)
}
// Str randomly picks and returns <n> count of chars from given string <s>.
// It also supports unicode string like Chinese/Russian/Japanese, etc.
func randomStr(s string, n int) string {
var (
b = make([]byte, n)
numberBytes = grand.B(n)
)
for i := range b {
b[i] = s[numberBytes[i]%36]
}
return gconv.UnsafeBytesToStr(b)
}
// getDataHashStr creates and returns hash bytes in 7 bytes with given data bytes.
func getDataHashStr(data []byte) string {
b := make([]byte, 7)
copy(b, []byte{'0', '0', '0', '0', '0', '0', '0'})
s := strconv.FormatUint(uint64(ghash.DJBHash(data)), 36)
copy(b[7-len(s):], s)
return gconv.UnsafeBytesToStr(b)
}

View File

@ -0,0 +1,38 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
package guid_test
import (
"github.com/gogf/gf/util/guid"
"testing"
)
func Benchmark_S(b *testing.B) {
for i := 0; i < b.N; i++ {
guid.S()
}
}
func Benchmark_New1(b *testing.B) {
for i := 0; i < b.N; i++ {
guid.New([]byte("123"))
}
}
func Benchmark_New2(b *testing.B) {
for i := 0; i < b.N; i++ {
guid.New([]byte("123"), []byte("456"))
}
}
func Benchmark_New3(b *testing.B) {
for i := 0; i < b.N; i++ {
guid.New([]byte("123"), []byte("456"), []byte("789"))
}
}

View File

@ -0,0 +1,34 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
package guid_test
import (
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/util/guid"
"testing"
"github.com/gogf/gf/test/gtest"
)
func Test_S(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
set := gset.NewStrSet()
for i := 0; i < 1000000; i++ {
s := guid.S()
t.Assert(set.AddIfNotExist(s), true)
t.Assert(len(s), 36)
}
})
}
func Test_New(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(len(guid.New([]byte("123"))), 36)
})
}