Cómo sembrar correctamente el generador de números aleatorios
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 time
genera 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?
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 randInt
funció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 Intn
para 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)
}
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.Read
fuente 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.Source
usando este método para evitar el costo de tener bloqueos que protejan la fuente. Las rand
funciones 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 Source
y 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!
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