【问题标题】:What is the fastest way to generate a long random string in Go?在 Go 中生成长随机字符串的最快方法是什么?
【发布时间】:2012-10-07 19:09:30
【问题描述】:

喜欢 [a-zA-Z0-9] 字符串:

na1dopW129T0anN28udaZ

或十六进制字符串:

8c6f78ac23b4a7b8c0182d

我指的是 2K 或更多字符。

【问题讨论】:

  • !为什么要一个 2Kbyte 的随机字符串?出于加密目的,1024 位(即 128 字节)就足够了。而且你很难得到真正随机的 2048 字节。
  • 操作系统是什么?在 Linux 上,您可以阅读 /dev/random/dev/urandom... kernel.org/doc/man-pages/online/pages/man4/random.4.html
  • 在不知道所需随机数的质量的情况下无法回答问题。所以我认为这不是出于密码学的目的。我也不明白“最快”。我可以想象它可能是一些游戏,但对随机字符串的要求使它看起来像密码。但再次说明为什么性能很重要。
  • 博客中的文章可能需要我们的回复)))
  • 它不是用于密码学,而是用于数据库工作负载生成器。

标签: string random go


【解决方案1】:

如果你想生成加密安全的随机字符串,我建议你看看this page。这是一个辅助函数,它从操作系统的随机源中读取 n 随机字节,然后使用这些字节对其进行 base64 编码。请注意,由于 base64,字符串长度会大于 n

package main

import(
    "crypto/rand"
    "encoding/base64"
    "fmt"
)

func GenerateRandomBytes(n int) ([]byte, error) {
    b := make([]byte, n)
    _, err := rand.Read(b)
    if err != nil {
        return nil, err
    }

    return b, nil
}

func GenerateRandomString(s int) (string, error) {
    b, err := GenerateRandomBytes(s)
    return base64.URLEncoding.EncodeToString(b), err
}

func main() {
    token, _ := GenerateRandomString(32)
    fmt.Println(token)
}

【讨论】:

    【解决方案2】:

    这实际上有点偏向于集合中的前 8 个字符(因为 255 不是 len(alphanum) 的倍数),但这会让你大部分时间到达那里。

    import (
        "crypto/rand"
    )
    
    func randString(n int) string {
        const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
        var bytes = make([]byte, n)
        rand.Read(bytes)
        for i, b := range bytes {
            bytes[i] = alphanum[b % byte(len(alphanum))]
        }
        return string(bytes)
    }
    

    【讨论】:

    • 核心crypto/rand.Reader在我的盒子上
    • 没错,问题没有指定字符串是否必须是加密随机的。如果他们不这样做,您的解决方案可能会更好。
    • 我已经多次遇到这个很棒的答案并且每次都使用它。这是另一种生成字符串的可能方式:play.golang.org/p/1GwSRsKIsd
    【解决方案3】:

    这在我的盒子上大约 200MBps。有明显的改进空间。

    type randomDataMaker struct {
        src rand.Source
    }
    
    func (r *randomDataMaker) Read(p []byte) (n int, err error) {
        for i := range p {
            p[i] = byte(r.src.Int63() & 0xff)
        }
        return len(p), nil
    }
    

    您只需使用io.CopyN 来生成您想要的字符串。显然你可以在途中调整字符集或其他任何东西。

    这个模型的好处是它只是一个io.Reader,所以你可以用它来制作任何东西。

    测试如下:

    func BenchmarkRandomDataMaker(b *testing.B) {
        randomSrc := randomDataMaker{rand.NewSource(1028890720402726901)}
        for i := 0; i < b.N; i++ {
            b.SetBytes(int64(i))
            _, err := io.CopyN(ioutil.Discard, &randomSrc, int64(i))
            if err != nil {
                b.Fatalf("Error copying at %v: %v", i, err)
            }
        }
    }
    

    在我的 2.2GHz i7 的一个核心上:

    BenchmarkRandomDataMaker       50000        246512 ns/op     202.83 MB/s
    

    编辑

    自从我编写了基准测试后,我想我会做明显的改进(减少随机调用的频率)。使用 1/8 的 rand 调用,它的运行速度提高了大约 4 倍,尽管它更丑:

    新版本:

    func (r *randomDataMaker) Read(p []byte) (n int, err error) {
        todo := len(p)
        offset := 0
        for {
            val := int64(r.src.Int63())
            for i := 0; i < 8; i++ {
                p[offset] = byte(val & 0xff)
                todo--
                if todo == 0 {
                    return len(p), nil
                }
                offset++
                val >>= 8
            }
        }
    
        panic("unreachable")
    }
    

    新基准:

    BenchmarkRandomDataMaker      200000        251148 ns/op     796.34 MB/s
    

    编辑 2

    去掉了强制转换为字节的掩码,因为它是多余的。更快地获得一笔好交易:

    BenchmarkRandomDataMaker      200000        231843 ns/op     862.64 MB/s
    

    (这比实际工作要容易得多sigh

    编辑 3

    这是今天在 irc 中出现的,所以我发布了一个库。此外,我的实际基准测试工具虽然对相对速度很有用,但它的报告不够准确。

    我创建了randbo,您可以在任何需要的地方重复使用它来生成随机流。

    【讨论】:

    • 但是因为它是 int63,所以每 8 个字节都有一个永远关闭的位,对吧?
    【解决方案4】:

    您可以使用 Go 包 uniuri 生成随机字符串(或查看源代码以了解他们是如何做到的)。你会想要使用:

    func NewLen(length int) string
    

    NewLen 返回一个新的随机字符串,由标准字符组成。

    或者,指定使用的字符集:

    func NewLenChars(length int, chars []byte) string
    

    【讨论】:

      【解决方案5】:

      在这里,Evan Shaw 的答案重新工作,没有偏向字符串的前 8 个字符。请注意,它使用了大量昂贵的big.Int 操作,所以可能没那么快!答案是加密强大。

      它使用rand.Int 生成一个大小正好合适的整数len(alphanum) ** n,然后进行有效的基本转换为基本len(alphanum)

      几乎可以肯定有一个更好的算法来解决这个问题,它需要保留一个小得多的余数并在必要时向其中添加随机字节。这将摆脱昂贵的长整数运算。

      import (
          "crypto/rand"
          "fmt"
          "math/big"
      )
      
      func randString(n int) string {
          const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
          symbols := big.NewInt(int64(len(alphanum)))
          states := big.NewInt(0)
          states.Exp(symbols, big.NewInt(int64(n)), nil)
          r, err := rand.Int(rand.Reader, states)
          if err != nil {
              panic(err)
          }
          var bytes = make([]byte, n)
          r2 := big.NewInt(0)
          symbol := big.NewInt(0)
          for i := range bytes {
              r2.DivMod(r, symbols, symbol)
              r, r2 = r2, r
              bytes[i] = alphanum[symbol.Int64()]
          }
          return string(bytes)
      }
      

      【讨论】:

      • 为什么每个人都会想到密码学?关键标准是速度而不是安全性。
      • 在您引入 crypto/rand.Reader 时,它的速度将低于 30MBps(在我的机器上),而超过 800MBps。
      猜你喜欢
      • 2013-08-21
      • 2020-12-01
      • 2016-09-23
      • 2018-07-03
      • 2012-02-05
      • 1970-01-01
      • 2015-07-17
      • 1970-01-01
      相关资源
      最近更新 更多