rand, el crate de números aleatorios más representativo de Rust, reparte operaciones cotidianas entre varios traits, por lo que se desarrolló urandom, con una superficie pública y de implementación más pequeña y una experiencia de uso coherente- Agrupa las operaciones de alto nivel en una sola estructura
Randomy sella el traitRng, priorizando la descubribilidad de la API y las optimizaciones internas por encima del soporte para generadores arbitrarios - Sin introducir nuevos algoritmos de números aleatorios, elige la función de salida de Xoshiro256 según el uso, y en un benchmark de generación de 1,000 valores
f64registró un rendimiento aproximadamente 31% mayor querand0.10.2 - El muestreo uniforme de enteros unifica las rutas reutilizables y de un solo uso con una única implementación sin sesgo que calcula el umbral de forma diferida, y en el benchmark del rango
500..20_000fue más rápida que las dos rutas derand - La salida bruta con semilla explícita garantiza reproducibilidad en arquitecturas compatibles y releases compatibles con SemVer, pero renuncia a la conexión de generadores arbitrarios y al vasto ecosistema de distribuciones e integraciones de terceros de
rand
API Random unificada
- Las operaciones útiles de
randestán repartidas entre varios traits- Para generar rangos aleatorios se necesita
RngExt, para elegir secuenciasIndexedRandom, y para mezclarSliceRandom rand0.10 ofrece helpers a nivel raíz comorand::random_rangepara llamadas puntuales- Si se mantiene un handle de RNG o se usan operaciones de secuencia como elegir o mezclar, todavía hay que encontrar métodos de varios traits
- Para generar rangos aleatorios se necesita
- Aunque el prelude reduce los imports, sigue siendo necesario saber a qué tipo se aplican los métodos de extensión —RNG, slices o iteradores—, por lo que son difíciles de descubrir solo con el autocompletado del IDE
- urandom coloca la API de consumo de alto nivel en una sola estructura wrapper
Randomurandom::new()creaRandom<urandom::rng::Xoshiro256Rng>uniform,chooseyshufflepueden llamarse desde el mismo objeto- El autocompletado permite ver
random,uniform,chance,choose,shuffle,sample, entre otros - Como todos son métodos propios, no hace falta encontrar ni importar traits de extensión de alto nivel
Un Rng sellado que elige optimización en lugar de extensibilidad
randtrata los traits de RNG de bajo nivel como puntos de extensión públicos, pero el traitRngdeurandomestá sellado, de modo que los generadores compatibles se eligen e implementan dentro del crate- No se pueden conectar generadores arbitrarios a
Random - Para agregar un nuevo generador hay que modificar el propio
urandom
- No se pueden conectar generadores arbitrarios a
- Si el objetivo es un mejor algoritmo, las opciones predeterminadas por rol ya están ocupadas por Xoshiro256 y ChaCha, y las recomendaciones también cambian lentamente
- Si aparece una mejor opción, puede adoptarse en un futuro release mayor
- Para compatibilidad con otros proyectos, lenguajes de programación, algoritmos heredados, hardware especial o generadores dedicados a simulaciones, no basta con que el generador sea el mismo
- También deben coincidir algoritmos relacionados como el muestreo uniforme y el mezclado, por lo que conviene más una implementación dedicada que implemente todo el contrato
- Gracias al trait sellado, se pueden agregar solo las operaciones primitivas que
urandomnecesita, sin diseñar ni documentar contratos de implementación para generadores desconocidos y casos excepcionales- Se pueden especializar generadores y algoritmos para que encajen entre sí, lo que permite algunas optimizaciones que no están disponibles en
rand
- Se pueden especializar generadores y algoritmos para que encajen entre sí, lo que permite algunas optimizaciones que no están disponibles en
- En la mayoría de las aplicaciones, elegir la entropía es más útil que implementar un nuevo PRNG
- Los generadores concretos exponen constructores nativos
from_seed - Se puede crear un
Randomcon una semilla explícita, comoChaCha12Rng::from_seed(seed) - No acepta implementaciones arbitrarias de RNG, pero conserva los puntos de extensión que se espera que necesiten los usuarios avanzados
- Los generadores concretos exponen constructores nativos
Mejoras de rendimiento obtenidas con el mismo algoritmo
urandomno usa un nuevo algoritmo de generación de números aleatorios- En sistemas de 64 bits,
urandom::new()para usos no criptográficos yrand::rngs::SmallRngusan la misma familia Xoshiro256 - Para usos criptográficos,
urandom::csprng()yrand::rngs::StdRngusan ChaCha12 - El generador interno de la función de conveniencia
rand::rng()también es ChaCha12
- En sistemas de 64 bits,
- La interfaz de generador de
randproporciona palabras enteras y llenado de bytes, por lo que incluso las distribuciones que necesitanf64solicitan primero unu64completo urandom::Rngproporciona no solonext_u32ynext_u64, sino tambiénnext_f32ynext_f64- Los números aleatorios de punto flotante necesitan menos bits aleatorios que una palabra completa
- Los generadores pueden sobrescribir estos métodos con funciones de salida más baratas
- La implementación de Xoshiro comparte la transición de estado, pero separa las rutas de salida
- Para
u64mantiene Xoshiro256++ - Para
u32y punto flotante usa el Xoshiro256+ más rápido, cuyos bits altos están diseñados para esos usos
- Para
- Los resultados de microbenchmarks que generaron 1,000 números aleatorios con
urandom1.0 yrand0.10.2 fueron los siguientes- Xoshiro
u64: 814ns en ambos - Xoshiro
u32:rand836ns,urandom788ns - Xoshiro
f64:rand1,033ns,urandom788ns - ChaCha12
f64:rand2,199ns,urandom2,011ns
- Xoshiro
- El rendimiento de extremo a extremo de Xoshiro
f64fue aproximadamente 31% mayor y el tiempo de ejecución 24% menor, pero la rutau64, que realiza la misma tarea, estuvo prácticamente empatada - ChaCha12 no sobrescribe
next_f64, por lo que su rendimiento es en general similar - Los tiempos exactos varían según la máquina y el compilador; las condiciones detalladas pueden consultarse en las notas completas del benchmark
Una única ruta integrada para el muestreo uniforme
- Si a los enteros se les aplica simplemente el operador módulo con la longitud del rango, aparece sesgo, por lo que el muestreo uniforme correcto de enteros debe rechazar parte de la salida del generador
- Calcular el umbral exacto de rechazo requiere una operación de módulo costosa
- Si el muestreador se reutiliza, puede asumirse como costo inicial de configuración
- Cuando solo se genera un valor, ese costo pesa relativamente más
randexpone esta diferencia mediante el traitUniformSampler- El
UniformIntgenerado calcula el umbral por adelantado para muestrear sin sesgo Rng::random_rangeusa hooks separadossample_singleosample_single_inclusivepara evitar la configuración inicial- Con las funciones predeterminadas, la ruta rápida de un solo uso usa un segundo algoritmo ligeramente sesgado
- La función opcional
unbiasedlo reemplaza por una versión iterativa más compleja
- El
urandomusa una única implementación de multiplicación y rechazo sin sesgo tanto para rangos reutilizables como de un solo uso, calculando el umbral de forma diferida- Sigue el método descrito en el artículo de 2018 de Daniel Lemire, Fast Random Integer Generation in an Interval
- En la mayoría de los rangos prácticos, el primer candidato se devuelve antes de cualquier división
- Si el primer candidato no puede devolverse, se calcula el umbral exacto y luego se repite sin sesgo
- También maneja la excepción
range == 0, cuando se solicita el rango completo
- La misma implementación maneja distribuciones reutilizables y rangos de un solo uso, sin métodos separados, sin un segundo algoritmo, sin costo de configuración anticipado y sin ruta rápida sesgada
- Los resultados del benchmark al extraer 1,000 valores del rango
500..20_000fueron los siguientesUniformIntreutilizable:rand1,098ns,urandom950ns- Rango de un solo uso:
rand1,079ns,urandom942ns
- Como los resultados de
randson con las funciones predeterminadas, la fila más rápida de un solo uso corresponde a una ruta ligeramente sesgada, mientras queurandomfue más rápido que ambas rutas manteniéndose sin sesgo
Reproducibilidad entre releases y arquitecturas
urandomtrata la reproducibilidad como parte de su contrato público- Dada la misma semilla explícita y el mismo orden de llamadas al RNG de bajo nivel, se conserva la salida bruta de los generadores deterministas
- Garantiza estabilidad en arquitecturas compatibles y releases compatibles con SemVer
- Un servidor de 64 bits y un cliente WebAssembly de 32 bits pueden usar la misma base de generador para reproducción
- Para mantener esta compatibilidad, se sacrifica rendimiento en arquitecturas de 32 bits
- Es una garantía más fuerte que la política de reproducibilidad de
rand- Los generadores portables y algoritmos de muestreo de
randpueden cambiar su salida en releases menores SmallRngyStdRngno son explícitamente portables y también pueden cambiar según la plataforma o el release de la biblioteca
- Los generadores portables y algoritmos de muestreo de
Costos de la elección y criterios de adopción
urandomreúne las operaciones comunes enRandompara que sean fáciles de encontrar sin traits de extensión- Diseña juntos generadores y distribuciones para implementar rutas de salida Xoshiro más baratas y una única ruta de muestreo uniforme sin sesgo
- El stream bruto estable de generadores con semilla explícita puede aprovecharse en juegos deterministas y simulaciones
- A cambio, no se pueden incorporar generadores arbitrarios y tampoco cuenta con la lista más amplia de distribuciones ni el ecosistema de integraciones de terceros que ofrece
rand - Si se necesita un ecosistema amplio,
randes la opción adecuada; si se prefiere una superficie de API pequeña, descubribilidad, optimizaciones integradas y una política fuerte de reproducibilidad, se puede elegirurandom - El paquete está disponible en crates.io, la documentación de la API y el código fuente en GitHub
1 comentarios
Opiniones en Lobste.rs
Hay razones de sobra para hacer un fork de
rand, pero el nombreurandomsuena como una librería relacionada con/dev/urandomEstoy de acuerdo con el planteamiento del problema, pero no me convence
pub fn new() -> Random<impl Rng + Clone>Parametrizar toda la aplicación como
Random<T> where T: Rngagrega trabajo engorroso, y los problemas de tiempo de compilación y los relacionados condynse vuelven serios. Preferiría questruct Randomtuviera un tipo concreto, o como segunda opciónstruct Random<T = rng::Xoshiro256Rng>Ya había hecho algo por mi cuenta por una frustración parecida, aunque no fue un fork, y tiene muchas menos funciones que
randMe da gusto que alguien que sintió el mismo problema que yo de verdad se haya puesto a resolverlo. Rust parece tener una tendencia extraña a empujar a la gente a crear librerías de sopa de traits
Los tipos de datos principales de la base de datos con la que trabajo necesitan implementar al menos 15 traits, así que el autocompletado es un desastre y la documentación también es confusa. He reducido un poco la cantidad de traits, pero a menudo termino bloqueado por problemas como dependencias circulares o por no poder escribir pruebas clave
Esta librería me recuerda tanto a las interfaces profundas de APOSD como al trabajo de criptografía de Filippo diseñado para que sea difícil equivocarse, y ambos son grandes elogios
urandom::new()no devuelve un generador de números aleatorios criptográficamente seguro, así que no es un diseño completamente a prueba de errores. Eso lo hace más confuso todavía, sobre todo porque/dev/urandomen Linux sí es seguroComo otra alternativa a
rand, estáfastrand, un generador de números aleatorios simple y rápido. Es más sencillo querandyurandom, pero también tiene menos funciones