一尘不染

在Go中生成长随机字符串的最快方法是什么?

go

类似于[a-zA-Z0-9]字符串:

na1dopW129T0anN28udaZ

或十六进制字符串:

8c6f78ac23b4a7b8c0182d

长期以来,我的意思是2K和更多字符。


阅读 213

收藏
2020-07-02

共1个答案

一尘不染

这在我的盒子上大约有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

编辑

自从编写基准测试以来,我认为我会做一些明显的改进(减少对随机性的呼唤)。使用rand的1/8调用,它的运行速度快了大约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

(这是这么多比真正的工作更容易 叹息

编辑3

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

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

2020-07-02