<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Stackoverflow高贊問答:如何一步步提升Go生成隨機字符串的效率

          共 1469字,需瀏覽 3分鐘

           ·

          2020-08-18 00:16

          假如我們要生成一個固定長度的隨機字符串,包含大小寫字母,沒有數字,沒有特殊字符串,那么我們怎么做呢?需要怎樣優(yōu)化,才會更簡單,更高效?在最終的方案之前,我們看看最常見的寫法是怎樣的,然后是如何一步步演進到最終的高效率方案的。好吧,先看下最原始的方案。

          常見做法(Runes)

          func?init()?{
          ????rand.Seed(time.Now().UnixNano())
          }

          var?letterRunes?=?[]rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

          func?RandStringRunes(n?int)?string?{
          ????b?:=?make([]rune,?n)
          ????for?i?:=?range?b?{
          ????????b[i]?=?letterRunes[rand.Intn(len(letterRunes))]
          ????}
          ????return?string(b)
          }

          這個實現比較簡單,二十六字母(大小寫),然后隨機取數,獲得隨機字符串。

          Bytes改進

          我們在最開始的時候進行了假設,我們的隨機字符串只包含大小寫字母,這樣的話,我們發(fā)現沒有必要使用rune類型存儲,因為在Golang(Go語言)UTF-8編碼下,英文字母和byte字節(jié)是一對一的。byte的本質是uint8類型,而rune本質是int32類型。我們改進后的代碼如下:

          const?letterBytes?=?"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

          func?RandStringBytes(n?int)?string?{
          ????b?:=?make([]byte,?n)
          ????for?i?:=?range?b?{
          ????????b[i]?=?letterBytes[rand.Intn(len(letterBytes))]
          ????}
          ????return?string(b)
          }

          仔細看上面的代碼,我們不光對rune類型進行了改進,還把原來的letter變量變成了常量,這樣len(letterBytes)也是一個常量,代碼的效率將大大提升。

          余數改進

          我們前面的方案都是通過調用rand.Intn()生成的隨機字符,這個rand.Intn()其實是委托調用的Rand.Intn(),而Rand.Intn()最終又是調用的Rand.Int31n()實現。相比我們直接調用rand.Int63()來說,rand.Intn()要慢很多。

          所以我們可以把rand.Intn()換成rand.Int63()來提高效率,為了不超過letterBytes的索引范圍,我們使用余數來保證。

          func?RandStringBytesRmndr(n?int)?string?{
          ????b?:=?make([]byte,?n)
          ????for?i?:=?range?b?{
          ????????b[i]?=?letterBytes[rand.Int63()?%?int64(len(letterBytes))]
          ????}
          ????return?string(b)
          }

          這種方式雖然快,但是有個缺點,就是每個字母的概率可能會不一樣,不過52個字母相比1<<63-1是在太小太小,所以在這種情況下,這個缺點可以忽略不計。

          Masking 掩碼

          基于前面的方案,我們可以進一步改進,使用隨機數的最低位保證字母的均等分配,也就是掩碼的方式。我們現在有52個字母,52用二進制表示就是52==110100b,所以我們可以只使用rand.Int63()返回最低的6位數就可以。為了保證平均分配,如果返回的只大于len(letterBytes)-1,則舍棄不用。

          const?letterBytes?=?"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
          const?(
          ????letterIdxBits?=?6????????????????????//?6?bits?to?represent?a?letter?index
          ????letterIdxMask?=?1<1?//?All?1-bits,?as?many?as?letterIdxBits
          )

          func?RandStringBytesMask(n?int)?string?{
          ????b?:=?make([]byte,?n)
          ????for?i?:=?0;?i?????????if?idx?:=?int(rand.Int63()?&?letterIdxMask);?idx?len(letterBytes)?{
          ????????????b[i]?=?letterBytes[idx]
          ????????????i++
          ????????}
          ????}
          ????return?string(b)
          }

          按照作者的推測,在52個字母的情況下,隨機到超過范圍的可能性(64-52)/64 = 0.19,按上面的代碼,如果超過范圍會重復生成,重復的10次的概率僅有1e-8。

          Masking 掩碼改進

          上一步的方案,我們使用rand.Int63()可以生成63個隨機位的數,但是我們只用了最低位的6個,有點浪費,因為獲取隨機數是我們整個代碼中最慢的部分?,F在我們有52個字母,意味著6位編碼字母索引即可滿足,所以我們使用rand.Int63()生成的隨機數可以被我們使用63/6=10次。

          const?letterBytes?=?"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
          const?(
          ????letterIdxBits?=?6????????????????????//?6?bits?to?represent?a?letter?index
          ????letterIdxMask?=?1<1?//?All?1-bits,?as?many?as?letterIdxBits
          ????letterIdxMax??=?63?/?letterIdxBits???//?#?of?letter?indices?fitting?in?63?bits
          )

          func?RandStringBytesMaskImpr(n?int)?string?{
          ????b?:=?make([]byte,?n)
          ????//?A?rand.Int63()?generates?63?random?bits,?enough?for?letterIdxMax?letters!
          ????for?i,?cache,?remain?:=?n-1,?rand.Int63(),?letterIdxMax;?i?>=?0;?{
          ????????if?remain?==?0?{
          ????????????cache,?remain?=?rand.Int63(),?letterIdxMax
          ????????}
          ????????if?idx?:=?int(cache?&?letterIdxMask);?idx?len(letterBytes)?{
          ????????????b[i]?=?letterBytes[idx]
          ????????????i--
          ????????}
          ????????cache?>>=?letterIdxBits
          ????????remain--
          ????}

          ????return?string(b)
          }

          把生成的63位的隨機數,分成10部分,每一部分都可以被我們使用,這樣我們調用rand.Int63()次數將大大降低,進而提升效率。

          rand Source 優(yōu)化

          rand.Rand其實是使用了一個rand.Source作為生成隨機數的源,這個rand.Source是個接口,正好有個func Int63() int64?方法。

          //?A?Source?represents?a?source?of?uniformly-distributed
          //?pseudo-random?int64?values?in?the?range?[0,?1<<63).
          type?Source?interface?{
          ????Int63()?int64
          ????Seed(seed?int64)
          }

          這正好是我們需要的,也夠我們用了。改進后代碼如下:

          var?src?=?rand.NewSource(time.Now().UnixNano())

          func?RandStringBytesMaskImprSrc(n?int)?string?{
          ????b?:=?make([]byte,?n)
          ????//?A?src.Int63()?generates?63?random?bits,?enough?for?letterIdxMax?characters!
          ????for?i,?cache,?remain?:=?n-1,?src.Int63(),?letterIdxMax;?i?>=?0;?{
          ????????if?remain?==?0?{
          ????????????cache,?remain?=?src.Int63(),?letterIdxMax
          ????????}
          ????????if?idx?:=?int(cache?&?letterIdxMask);?idx?len(letterBytes)?{
          ????????????b[i]?=?letterBytes[idx]
          ????????????i--
          ????????}
          ????????cache?>>=?letterIdxBits
          ????????remain--
          ????}

          ????return?string(b)
          }

          原來的rand.Int63()是整個rand包全局的,而且支持安全高并發(fā),所以速度比較慢?,F在我們自己創(chuàng)建的這個src只有我們自己用,所以效率比較高。

          strings.Builder 改進

          這個是G0 1.10 新增的功能,提升字符串拼接的效率。

          經過改進后,代碼如下:

          func?RandStringBytesMaskImprSrcSB(n?int)?string?{
          ????sb?:=?strings.Builder{}
          ????sb.Grow(n)
          ????//?A?src.Int63()?generates?63?random?bits,?enough?for?letterIdxMax?characters!
          ????for?i,?cache,?remain?:=?n-1,?src.Int63(),?letterIdxMax;?i?>=?0;?{
          ????????if?remain?==?0?{
          ????????????cache,?remain?=?src.Int63(),?letterIdxMax
          ????????}
          ????????if?idx?:=?int(cache?&?letterIdxMask);?idx?len(letterBytes)?{
          ????????????sb.WriteByte(letterBytes[idx])
          ????????????i--
          ????????}
          ????????cache?>>=?letterIdxBits
          ????????remain--
          ????}

          ????return?sb.String()
          }

          使用unsafe包模擬 strings.Builder

          strings.Builder的原理其實很簡單,是內置了一個[]byte存儲字符,最終轉換為string的時候為了避免拷貝,使用了unsafe包。

          //?String?returns?the?accumulated?string.
          func?(b?*Builder)?String()?string?{
          ????return?*(*string)(unsafe.Pointer(&b.buf))
          }

          以上這些我們可以自己來做,看看我們重寫后的代碼。

          func?RandStringBytesMaskImprSrcUnsafe(n?int)?string?{
          ????b?:=?make([]byte,?n)
          ????//?A?src.Int63()?generates?63?random?bits,?enough?for?letterIdxMax?characters!
          ????for?i,?cache,?remain?:=?n-1,?src.Int63(),?letterIdxMax;?i?>=?0;?{
          ????????if?remain?==?0?{
          ????????????cache,?remain?=?src.Int63(),?letterIdxMax
          ????????}
          ????????if?idx?:=?int(cache?&?letterIdxMask);?idx?len(letterBytes)?{
          ????????????b[i]?=?letterBytes[idx]
          ????????????i--
          ????????}
          ????????cache?>>=?letterIdxBits
          ????????remain--
          ????}

          ????return?*(*string)(unsafe.Pointer(&b))
          }

          效果和使用strings.Builder一樣,而且看起來更簡潔了。

          Benchmark 性能測試

          以后,我們通過一步步的改進代碼,提升效率,現在我們通過Benchmark測試看下這些方法的效果。

          BenchmarkRunes-4?????????????????????2000000????723?ns/op???96?B/op???2?allocs/op
          BenchmarkBytes-4?????????????????????3000000????550?ns/op???32?B/op???2?allocs/op
          BenchmarkBytesRmndr-4????????????????3000000????438?ns/op???32?B/op???2?allocs/op
          BenchmarkBytesMask-4?????????????????3000000????534?ns/op???32?B/op???2?allocs/op
          BenchmarkBytesMaskImpr-4????????????10000000????176?ns/op???32?B/op???2?allocs/op
          BenchmarkBytesMaskImprSrc-4?????????10000000????139?ns/op???32?B/op???2?allocs/op
          BenchmarkBytesMaskImprSrcSB-4???????10000000????134?ns/op???16?B/op???1?allocs/op
          BenchmarkBytesMaskImprSrcUnsafe-4???10000000????115?ns/op???16?B/op???1?allocs/op

          僅僅從runebyte的改進,我們就獲得了** 24%的提升,內存占用降低了三分之一** 。

          使用rand.Int63替換掉原來的rand.Intn,我們又獲得了近20%的提升。

          單純的使用掩碼,因為重復獲取可用索引的問題,性能下降了** -22%**。

          但是當我們對 Masking 掩碼 進行改進,分為10部分緩存的時候,我們獲得了3倍的提升。

          使用rand.Source?代替?rand.Rand, 我們再次獲得了21%的提升。

          使用strings.Builder,速度提升雖然只有3.5%,但是內存分配降低了50%?。

          最后,通過unsafe包精簡重寫了strings.Builder的功能,我們又獲得了14%的提升。

          最終,RandStringBytesMaskImprSrcUnsafeRandStringRunes6.3倍,并且只使用了六分之一的內存和一半的內存分配,我們就完成了任務。

          結語

          這是一篇stackoverflow的文章,有人提問 How to generate a random string of a fixed length in Go? ,icza 做了非常精彩的回答,我把整個翻譯下來加以整理分享給大家。

          這是一篇非常棒的文章,它的意義不光是回答這個問題,還有幫助我們建立如何一步步優(yōu)化的思路以及追求極致的極客精神。

          與大家共勉。

          原文地址:https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go




          推薦閱讀

          • xxx



          學習交流 Go 語言,掃碼回復「進群」即可


          站長 polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術

          職場和創(chuàng)業(yè)經驗


          Go語言中文網

          每天為你

          分享 Go 知識

          Go愛好者值得關注


          瀏覽 35
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  内射七八高清视频 | 操屄视频在线观看 | 成人人妻视频 | 国产精品久久久久久一级毛片 | 岛国av在线免费 丁香五月激情中文 |