优化大量生成手机号码所使用的时间,取得较好的成果
配置
既然是计算时间,那么肯定受电脑配置影响,这里贴下配置
win10
随机字符串生成前景
以下省略计算时间的代码
1 2 3 4 5 6 7 8 9 10
| func main() { before := time.Now() fmt.Println(before.Format("2006-01-02 15:04:05"))
after := time.Now() fmt.Println(after.Format("2006-01-02 15:04:05")) fmt.Println(after.Sub(before)) }
|
单纯跑十亿次循环
1 2 3 4 5 6 7 8
| idx := 0 for i := 0; i < 1000000000; i++{ idx++ }
|
十亿次随机获取手机号码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var headerNums = [...]string{"139", "138", "137", "136", "135", "134", "159", "158", "157", "150", "151", "152", "188", "187", "182", "183", "184", "178", "130", "131", "132", "156", "155", "186", "185", "176", "133", "153", "189", "180", "181", "177"} var headerNumsLen = len(headerNums)
func randomPhone() string { header := headerNums[rand.Intn(headerNumsLen)] body := fmt.Sprintf("%08d", rand.Intn(99999999)) phone := header + body return phone }
func main(){ rand.Seed(time.Now().UTC().UnixNano()) for i := 0; i < 1000000000; i++{ randomPhone() } }
|
可以看到时间差不多花在了生成随机数那里
协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var wg sync.WaitGroup for i := 0; i < 10; i++{ wg.Add(1) go func() { for i := 0; i < 100000000; i++{ randomPhone() } defer wg.Done() }() } wg.Wait()
|
可以明显看到时间花长了,这一部分应该是rand
内部有互斥锁的原因,并发情况下竞争锁
sync.mutex
锁的过程大概如下:
有两种状态:
- 正常状态:等待锁的goroutine按照FIFO的顺序获得锁,如有新请求的锁,则与被唤醒的竞争锁,失败则加入队首,如果一个等待的goroutine超过1ms没有获取锁,那么它将会把锁转变为饥饿模式。
- 饥饿状态:把锁交给等待队列中的第一个(已经unlock状态),新来的goroutine将不会尝试去获得锁,即使锁看起来是unlock状态, 也不会去尝试自旋操作,而是放在等待队列的尾部。
协程改进
既然那么多个goroutine会竞争锁,所以我们可以给每个goroutine开一把锁,也就是多个rand实例,那么就不用竞争锁了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package main
import ( "fmt" "math/rand" "runtime" "sync" "time" ) var headerNums = [...]string{"139", "138", "137", "136", "135", "134", "159", "158", "157", "150", "151", "152", "188", "187", "182", "183", "184", "178", "130", "131", "132", "156", "155", "186", "185", "176", "133", "153", "189", "180", "181", "177"} var headerNumsLen = len(headerNums)
func randomPhone(generator *rand.Rand) string { header := headerNums[generator.Intn(headerNumsLen)] body := fmt.Sprintf("%08d", generator.Intn(99999999)) phone := header + body return phone }
func main() { before := time.Now() fmt.Println(before.Format("2006-01-02 15:04:05"))
var wg sync.WaitGroup nCPU := runtime.NumCPU() runtime.GOMAXPROCS(nCPU)
loop := 1000000000 / nCPU for i := 0; i < nCPU; i++{ generator := rand.New(rand.NewSource(time.Now().UnixNano() + int64(i*1000))) wg.Add(1) go func(generator *rand.Rand) { for j := 0; j < loop; j++{ randomPhone(generator) } defer wg.Done() }(generator) } wg.Wait()
after := time.Now() fmt.Println(after.Format("2006-01-02 15:04:05")) fmt.Println(after.Sub(before)) }
|
改善字符串生成的方法
rand.Intn
可以看到,rand.Intn
函数最终会调用Rand.Int63
,所以我们可以直接调用该函数即可
利用掩码提高效率
我们现在有10个数字,10用二进制表示就是1010
,所以我们可以只使用Rand.Int63()
返回最低的4位数就可以。为了保证平均,如果返回的只大于len(letterBytes)-1
,则舍弃不用
Rand.Int63
可以生成63个随机位的数,所以剩下的位数我们依旧可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| const letterBytes = "0123456789" var src = rand.NewSource(time.Now().UnixNano())
var headerNums = [...]string{"139", "138", "137", "136", "135", "134", "159", "158", "157", "150", "151", "152", "188", "187", "182", "183", "184", "178", "130", "131", "132", "156", "155", "186", "185", "176", "133", "153", "189", "180", "181", "177"} var headerNumsLen = len(headerNums)
const ( letterIdxBits = 4 letterIdxMask = 1<<letterIdxBits - 1 ) const ( headerIdxBits = 6 headerIdxMask = 1<<headerIdxBits - 1 )
func getHeaderIdx(cache int64) int { for cache > 0{ idx := int(cache & headerIdxMask) if idx < headerNumsLen{ return idx } cache >>= headerIdxBits } return rand.Intn(headerNumsLen) }
func randomPhone() string { b := make([]byte, 12) cache := src.Int63() headerIdx := getHeaderIdx(cache) for i := 0; i < 3; i++{ b[i] = headerNums[headerIdx][i] } for i := 3; i < 12 ; { if cache == 0{ cache = src.Int63() } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i++ } cache >>= letterIdxBits } return string(b) }
|
使用strings.Builder提升字符串拼接速度(可选)
这个是G0 1.10 新增的功能,提升字符串拼接的效率,strings.Builder
的原理其实很简单,是内置了一个[]byte
存储字符,最终转换为string
的时候为了避免拷贝,使用了unsafe
包,可以把string(b)
变为*(*string)(unsafe.Pointer(&b))
这里字符串长度比较短,所以影响不大
最后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| package main
import ( "fmt" "math/rand" "time" )
const letterBytes = "0123456789" const ( letterIdxBits = 4 letterIdxMask = 1<<letterIdxBits - 1 ) var src = rand.NewSource(time.Now().UnixNano())
var headerNums = [...]string{"139", "138", "137", "136", "135", "134", "159", "158", "157", "150", "151", "152", "188", "187", "182", "183", "184", "178", "130", "131", "132", "156", "155", "186", "185", "176", "133", "153", "189", "180", "181", "177"} var headerNumsLen = len(headerNums) const ( headerIdxBits = 6 headerIdxMask = 1<<headerIdxBits - 1 )
func getHeaderIdx(cache int64) int { for cache > 0{ idx := int(cache & headerIdxMask) if idx < headerNumsLen{ return idx } cache >>= headerIdxBits } return rand.Intn(headerNumsLen) }
func randomPhone() string { b := make([]byte, 12) cache := src.Int63() headerIdx := getHeaderIdx(cache) for i := 0; i < 3; i++{ b[i] = headerNums[headerIdx][i] } for i := 3; i < 12 ; { if cache == 0{ cache = src.Int63() } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i++ } cache >>= letterIdxBits } return string(b) }
func main() { before := time.Now() fmt.Println(before.Format("2006-01-02 15:04:05"))
for i := 0; i < 1000000000; i++{ randomPhone() }
after := time.Now() fmt.Println(after.Format("2006-01-02 15:04:05")) fmt.Println(after.Sub(before)) }
|
速度提升还是很明显的,在配置低的电脑上面差距会更大
如果我们加上协程呢,同时注意去掉var src = rand.NewSource(time.Now().UnixNano())
避免竞争锁
调用代码和没有使用改善字符串生成的方法差不多
效果强劲
最终成果
参考:
sync.mutex 源代码分析 | 鸟窝
图解Go里面的互斥锁mutex了解编程语言核心实现源码 - 掘金
一步步提升Go语言生成随机字符串的效率 | 飞雪无情的博客