Cómo sembrar correctamente el generador de números aleatorios

Resuelto copperMan asked hace 12 años • 12 respuestas

Estoy intentando generar una cadena aleatoria en Go y aquí está el código que he escrito hasta ahora:

package main

import (
    "bytes"
    "fmt"
    "math/rand"
    "time"
)

func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}

Mi implementación es muy lenta. La siembra usando timegenera el mismo número aleatorio durante un tiempo determinado, por lo que el ciclo se repite una y otra vez. ¿Cómo puedo mejorar mi código?

copperMan avatar Sep 07 '12 22:09 copperMan
Aceptado

Cada vez que configuras la misma semilla, obtienes la misma secuencia. Entonces, por supuesto, si configura la semilla en el tiempo en un bucle rápido, probablemente lo llamará con la misma semilla muchas veces.

En su caso, mientras llama a su randIntfunción hasta que tiene un valor diferente, está esperando que cambie el tiempo (según lo devuelto por Nano).

Como ocurre con todas las bibliotecas pseudoaleatorias , debe configurar la semilla solo una vez, por ejemplo, al inicializar su programa, a menos que necesite específicamente reproducir una secuencia determinada (lo que generalmente solo se hace para depuración y pruebas unitarias).

Después de eso, simplemente llama Intnpara obtener el siguiente número entero aleatorio.

Mueva la rand.Seed(time.Now().UTC().UnixNano())línea de la función randInt al inicio de main y todo será más rápido. Y perder la .UTC()llamada desde:

UnixNano devuelve t como tiempo Unix, el número de nanosegundos transcurridos desde el 1 de enero de 1970 UTC.

Tenga en cuenta también que creo que puede simplificar la construcción de cadenas:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    bytes := make([]byte, l)
    for i := 0; i < l; i++ {
        bytes[i] = byte(randInt(65, 90))
    }
    return string(bytes)
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
Denys Séguret avatar Sep 07 '2012 15:09 Denys Séguret

Editar: este problema se solucionó a partir de la versión 1.20 de Go y ya no es necesario inicializar la fuente aleatoria usted mismo.

No entiendo por qué la gente siembra con un valor de tiempo. En mi experiencia, esto nunca ha sido una buena idea. Por ejemplo, aunque el reloj del sistema puede estar representado en nanosegundos, la precisión del reloj del sistema no es de nanosegundos.

Este programa no debe ejecutarse en el patio de juegos de Go, pero si lo ejecuta en su máquina obtendrá una estimación aproximada del tipo de precisión que puede esperar. Veo incrementos de aproximadamente 1000000 ns, es decir, incrementos de 1 ms. Son 20 bits de entropía que no se utilizan. ¿¡Todo el tiempo los bits altos son en su mayoría constantes!? Aproximadamente ~24 bits de entropía durante un día, lo cual es de fuerza bruta (lo que puede crear vulnerabilidades).

El grado en que esto le importe variará, pero puede evitar los problemas de los valores de semilla basados ​​en el reloj simplemente usando como crypto/rand.Readfuente para su semilla. Le brindará esa calidad no determinista que probablemente esté buscando en sus números aleatorios (incluso si la implementación real en sí se limita a un conjunto de secuencias aleatorias distintas y deterministas).

import (
    crypto_rand "crypto/rand"
    "encoding/binary"
    math_rand "math/rand"
)

func init() {
    var b [8]byte
    _, err := crypto_rand.Read(b[:])
    if err != nil {
        panic("cannot seed math/rand package with cryptographically secure random number generator")
    }
    math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

Como nota al margen pero en relación con tu pregunta. Puedes crear el tuyo propio rand.Sourceusando este método para evitar el costo de tener bloqueos que protejan la fuente. Las randfunciones de utilidad del paquete son convenientes pero también usan bloqueos debajo del capó para evitar que la fuente se use simultáneamente. Si no lo necesita, puede evitarlo creando el suyo propio Sourcey utilizándolo de forma no concurrente. De todos modos, NO deberías resembrar tu generador de números aleatorios entre iteraciones, nunca fue diseñado para usarse de esa manera.


Editar: Solía ​​trabajar en ITAM/SAM y el cliente que construimos (luego) usaba una semilla basada en reloj. Después de una actualización de Windows, muchas máquinas de la flota de la empresa se reiniciaron aproximadamente al mismo tiempo. Esto provocó un ataque DoS involuntario en la infraestructura del servidor ascendente porque los clientes estaban usando el tiempo de actividad del sistema para generar aleatoriedad y estas máquinas terminaron eligiendo más o menos aleatoriamente el mismo intervalo de tiempo para informar. Estaban destinados a difuminar la carga durante un período de una hora más o menos pero eso no sucedió. ¡Sembra responsablemente!

John Leidegren avatar Feb 02 '2019 09:02 John Leidegren

solo para descartarlo para la posteridad: a veces puede ser preferible generar una cadena aleatoria utilizando una cadena de conjunto de caracteres inicial. Esto es útil si se supone que un humano debe ingresar la cadena manualmente; excluir 0, O, 1 y l puede ayudar a reducir los errores del usuario.

var alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"

// generates a random string of fixed size
func srand(size int) string {
    buf := make([]byte, size)
    for i := 0; i < size; i++ {
        buf[i] = alpha[rand.Intn(len(alpha))]
    }
    return string(buf)
}

y normalmente coloco la semilla dentro de un init()bloque. Están documentados aquí: http://golang.org/doc/ Effective_go.html#init

jorelli avatar Sep 07 '2012 17:09 jorelli