Подтвердить что ты не робот

Каков самый быстрый способ генерации длинной случайной строки в Go?

Как строка [a-zA-Z0-9]:

na1dopW129T0anN28udaZ

или шестнадцатеричная строка:

8c6f78ac23b4a7b8c0182d

По длительности я имею в виду 2K и более символов.

4b9b3361

Ответ 1

Это примерно 200 Мбит/с на моей коробке. Там есть очевидная возможность для улучшения.

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,7 ГГц 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

(это намного проще, чем реальный вздох)

РЕДАКТИРОВАТЬ 3

Сегодня это появилось в irc, поэтому я выпустил библиотеку. Кроме того, мой фактический инструмент сравнения, хотя и полезен для относительной скорости, недостаточно достоверен в своих отчетах.

Я создал randbo, чтобы вы могли повторно использовать произвольные потоки, где бы они вам ни понадобились.

Ответ 2

Вы можете использовать пакет Go uniuri для генерации случайных строк (или просмотреть исходный код, чтобы увидеть, как они это делают), Вы хотите использовать:

func NewLen(length int) string

NewLen возвращает новую случайную строку предоставленной длины, состоящую из стандартных символов.

Или, чтобы указать набор используемых символов:

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

Ответ 3

Это на самом деле немного предвзято по отношению к первым 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)
}

Ответ 4

Здесь Эван Шоу ответил на повторную работу без смещения в сторону первых 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)
}

Ответ 5

Если вы хотите генерировать криптографически безопасную случайную строку, я рекомендую вам взглянуть на на этой странице. Вот вспомогательная функция, которая считывает n случайные байты из источника случайности вашей ОС, а затем использует эти байты для base64encode. Обратите внимание, что длина строки будет больше, чем n из-за base64.

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)
}

Ответ 6

Я сделал несколько тестов, и пакет Dustin randbo был самым быстрым, что я видел. Я искал его и делал несколько оптимизаций, поэтому он стал быстрее: GitHub