Probé un script bash, pero me llevó demasiado tiempo crear un archivo simple de 1 MB. Creo que la respuesta está en usar /dev/random
o /dev/urandom
, pero otras publicaciones aquí solo muestran cómo agregar todo tipo de datos a un archivo usando estas cosas, pero quiero agregar solo números.
Entonces, ¿hay un comando que pueda usar para crear un archivo aleatorio de tamaño 1 GB que contenga solo números entre 0 y 9?
Editar: quiero que la salida sea algo como esto
0 1 4 7 ..... 9
8 7 5 8 ..... 8
....
....
8 7 5 3 ..... 3
El rango es 0 - 9, lo que significa solo números 0, 1, 2, 3, 4, 5, 6, 7, 8 y 9. También necesito que estén separados por espacios y 100 por línea, hasta el n
número de líneas. Esto es algo que no me importa, quiero que mi tamaño final sea de 1 GB.
Editar: estoy usando Ubuntu 16.04 LTS
yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Respuestas:
Esto es parcialmente una respuesta irónica, debido al título de la pregunta.
Cuando busca "la forma más rápida de ..." , la respuesta es casi siempre una herramienta especializada. Esta "respuesta" muestra una de esas herramientas, para que pueda experimentar.
Esta no es una respuesta seria, porque no debe buscar herramientas especializadas para trabajos que solo realiza una vez, o muy raramente. Verá, terminará pasando más tiempo buscando herramientas y aprendiendo sobre ellas, en lugar de hacer cosas. Los shells y las utilidades les gustan
bash
yawk
no son los más rápidos, pero por lo general puedes escribir una sola línea para lograr el trabajo, gastando solo unos segundos.perl
También se pueden utilizar mejores lenguajes de scripting , aunque la curva de aprendizajeperl
es empinada, y dudo en recomendarlo para tales propósitos, porque me han traumatizado los terribles proyectos de Perl.python
por otro lado, está ligeramente perjudicado por su E / S bastante lenta; Sin embargo, solo es un problema cuando filtra o genera gigabytes de datos.En cualquier caso, el siguiente programa de ejemplo C89 (que usa POSIX.1 para un reloj de mayor precisión solo si está disponible) debería alcanzar una tasa de generación de aproximadamente 100 MB / s (probado en Linux en una computadora portátil con un procesador Intel i5-4200U, canalizando la salida a
/dev/null
), utilizando un generador de números pseudoaleatorios bastante bueno. (La salida debe pasar todas las pruebas BigCrunch, excepto la prueba MatrixRank, ya que el código usa xorshift64 * y el método de exclusión para evitar sesgos en los dígitos).dígitos-decimales.c:
Podemos hacerlo mucho más rápido si cambiamos a un búfer de línea, y
fwrite()
una vez en lugar de generar cada dígito a la vez. Tenga en cuenta que todavía mantenemos el flujo completamente protegido, para evitar escrituras parciales (sin potencia de dos) si la salida es un dispositivo de bloque.Nota: ambos ejemplos se editaron el 18-11-2016 para garantizar una distribución uniforme de los dígitos (se excluye el cero; consulte, por ejemplo, aquí para ver una comparación y detalles sobre varios generadores de números pseudoaleatorios).
Compilar usando por ejemplo
y opcionalmente instalar todo el sistema para
/usr/bin
usarToma el número de dígitos por línea y el número de líneas. Porque
1000000000 / 100 / 2 = 5000000
(cinco millones; bytes totales divididos por columnas divididas por 2), puede usarpara generar el gigabyte del tamaño
digits.txt
deseado por el OP.Tenga en cuenta que el programa en sí está escrito más teniendo en cuenta la legibilidad que la eficiencia. Mi intención aquí no es mostrar la eficiencia del código; de todos modos, usaría POSIX.1 y E / S de bajo nivel, en lugar de interfaces C genéricas, sino permitirle ver fácilmente qué tipo de equilibrio hay con el esfuerzo invertido en el desarrollo de herramientas dedicadas en comparación con su rendimiento, en comparación con líneas simples o shell corto o scriptlets awk.
Al usar la biblioteca GNU C, llamar a la
fputc()
función para cada salida de caracteres conlleva una sobrecarga muy pequeña (de una llamada de función indirecta, o condicionales; laFILE
interfaz es en realidad bastante compleja y versátil, ya ves). En esta computadora portátil Intel Core i5-4200U en particular, la redirección de la salida a/dev/null
la primera versión (fputc) demora aproximadamente 11 segundos, mientras que la versión de línea a la vez demora solo 1.3 segundos.A menudo escribo tales programas y generadores solo porque me gusta jugar con grandes conjuntos de datos. Soy raro de esa manera. Por ejemplo, una vez escribí un programa para imprimir todos los valores positivos finitos de coma flotante IEEE-754 en un archivo de texto, con suficiente precisión para producir exactamente el mismo valor cuando se analiza. El archivo tenía unos pocos gigabytes de tamaño (quizás 4G más o menos); no hay tantos positivos finitos
float
como uno podría pensar. Utilicé esto para comparar implementaciones que leen y analizan dichos datos.Para casos de uso normal, como el que tiene OP, los scripts de shell y scriptlets y one-liners son el mejor enfoque. Menos tiempo dedicado a realizar la tarea general. (Excepto si necesitan un archivo diferente cada día más o menos, o si hay muchas personas que necesitan un archivo diferente, en el que, en casos excepcionales, una herramienta dedicada como la anterior, podría justificar el esfuerzo invertido).
fuente
mmap()
sea la ruta más fácil hacia la mejor velocidad de E / S, ¡pero es un punto de referencia antes de hacer cualquier reclamo!write()
, el uso , suelen ser más rápidas quemmap()
.fwrite()
No es mucho más lento. Sí, lo he comparado (solo que no para este ejemplo en particular);write()
en fragmentos grandes (262144, 524288 o 1048576 bytes) tiende a superar a los otros métodos. La versión defputc()
implementado en la biblioteca GNU C (que también he comparado ampliamente) es lenta por varias razones; en particular, la implementación tiene que hacer saltos condicionales o llamadas indirectas para cada carácter agregado; esa ligera sobrecarga incurrida tan a menudo se suma./dev/null
. El guión de Stéphane Chazelas tarda unos 52 segundos; fragmento de perl (incluido elhead
filtrado) unos 58 segundos; sushuf
fragmento (con el tiempo correcto; solo mide el tiempo de shuf, suponiendo que la pasta no demore más) toma aproximadamente 69 segundos. El programa C ++ 11 de James Hollis, línea por línea, lleva 14 segundos. El programa anterior lleva 10 segundos.Esta:
(suponiendo una
head
implementación que soporte-c
) parece ser razonablemente rápido en mi sistema.tr
traduce todo el rango de bytes (0 a 255, 0 a 0377 en octal): los 25 primeros bytes como 0, los 25 siguientes como 1 ... luego 25 9 el resto (250 a 255) a "x" que luego descarte (contr -d x
) ya que queremos una distribución uniforme (suponiendo que/dev/urandom
tenga una distribución uniforme en sí) y, por lo tanto, no proporcione un sesgo a algunos dígitos.Eso produce un dígito para el 97% de los bytes de
/dev/urandom
.fold -w 1
lo hace un dígito por línea.paste -s
se llama con una lista de separadores que consta de 99 caracteres de espacio y un carácter de nueva línea, para tener 100 dígitos separados por espacio en cada línea.head -c1G
obtendrá el primer GiB (2 30 ) de eso. Tenga en cuenta que la última línea se truncará y no se delimitará. Podría truncar a 2 30 -1 y agregar la nueva línea que falta a mano, o truncar a 10 9 bytes, que es 50 millones de esas líneas de 200 bytes (head -n 50000000
también lo haría un comando estándar / portátil).Estos tiempos (obtenidos
zsh
en un sistema de cuatro núcleos), dan una indicación de dónde se gasta el tiempo de CPU:El primero
tr
es el cuello de botella, la mayor parte del tiempo pasado en el núcleo (supongo que para la generación de números aleatorios). El tiempo está más o menos en línea con la velocidad de la que puedo obtener bytes/dev/uramdom
(aproximadamente 19MiB / sy aquí producimos 2 bytes por cada 0,97 bytes de / dev / urandom a una velocidad de 32MiB / s).fold
parece estar gastando una cantidad irrazonable de tiempo de CPU (15s) solo para insertar un carácter de nueva línea después de cada byte, pero eso no afecta el tiempo general ya que funciona en una CPU diferente en mi caso (agregar la-b
opción hace que sea un poco más eficiente,dd cbs=1 conv=unblock
parece una mejor alternativa).Puede eliminar
head -c1G
y afeitarse unos segundos estableciendo un límite en el tamaño del archivo (limit filesize 1024m
conzsh
oulimit -f "$((1024*1024))"
con la mayoría de los otros shells (incluidoszsh
)) en su lugar en un subshell.Eso podría mejorarse si extrajéramos 2 dígitos para cada byte, pero necesitaríamos un enfoque diferente para eso. Lo anterior es muy eficiente porque
tr
solo busca cada byte en una matriz de 256 bytes. No puede hacer eso por 2 bytes a la vez, y usar cosas comohexdump -e '1/1 "%02u"'
esas calcula la representación de texto de un byte usando algoritmos más complejos sería más costoso que la generación de números aleatorios en sí. Aún así, si, como en mi caso, tiene núcleos de CPU cuyo tiempo de sobra, aún puede reducir algunos segundos:Con:
Obtengo (tenga en cuenta que aquí hay 1,000,000,000 de bytes en lugar de 1,073,741,824):
Más tiempo de CPU en general, pero mejor distribuido entre mis 4 núcleos de CPU, por lo que termina tomando menos tiempo de reloj de pared. El cuello de botella es ahora
hexdump
.Si utilizamos en
dd
lugar de la líneafold
, podemos reducir la cantidad de trabajo quehexdump
hay que hacer y mejorar el equilibrio de trabajo entre las CPU:(aquí suponiendo GNU
dd
para suiflag=fullblock
ystatus=none
) que da:Volver a la generación de números aleatorios es el cuello de botella.
Ahora, como señaló @OleTange, si tiene la
openssl
utilidad, puede usarla para obtener un generador de bytes pseudoaleatorio más rápido (especialmente en los procesadores que tienen instrucciones AES).en mi sistema arroja 15 veces más bytes por segundo que
/dev/urandom
. (No puedo comentar cómo se compara en términos de fuente de aleatoriedad criptográficamente segura si eso se aplica a su caso de uso).Ahora da:
vuelve a
hexdump
ser el cuello de botella.Como todavía tengo CPU de sobra, puedo ejecutar 3 de ellas
hexdump
en paralelo.(
<&3
se necesita para shells que no sean loszsh
comandos de cierre 'stdin on / dev / null cuando se ejecuta en segundo plano).Ahora bajó a 6.2 segundos y mis CPU se utilizaron casi por completo.
fuente
perl
variante que era significativamente más lenta de todos modos. No puedo obtener 2 dígitos por byte con ese enfoque tr | fold | paste.bc
(luego suelte los 0, 1 o 2 dígitos más significativos).Si tiene
shuf
disponible (los recientes GNU coreutils lo hacen) puede hacer esto:En mi VM, esto ahora es un poco más lento que la respuesta de Stéphane en un factor de 3: 4.
fuente
shuf
en mi empresa, la PC no tiene-r
,fmt
no tiene-g
tambiénpaste
/printf
truco - gracias. Su respuesta ahora es aparentemente más rápida.Si no necesita una aleatoriedad de muy alta calidad, y una distribución casi uniforme es lo suficientemente buena, puede ir muy rápido, especialmente en una CPU moderna con vectores enteros SIMD eficientes como x86 con SSE2 o AVX2.
Esto es como la respuesta de @ NominalAnimal ya que ambos teníamos la misma idea, pero vectorizados manualmente para x86. (Y con números aleatorios de peor calidad, pero probablemente lo suficientemente buenos para muchos casos de uso). Esto se ejecuta aproximadamente 15 o 30 veces más rápido que el código de @ Nominal, a ~ 13 GB / s de salida ASCII en un Intel Haswell de 2.5 GHz CPU con AVX2. Eso es aún menos que el ancho de banda máximo teórico de la memoria principal (DDR3-1600 de doble canal es de aproximadamente 25.6GB / s), pero estaba cronometrando la escritura en / dev / null, por lo que en realidad solo está reescribiendo un búfer que permanece caliente en la caché. Skylake debería ejecutar este mismo código significativamente más rápido que Haswell (consulte la parte inferior de esta respuesta).
Suponiendo que realmente cuente con un cuello de botella en E / S en el disco o conecte esto en alguna parte, una implementación rápida significa que su CPU ni siquiera tiene que registrar un reloj más alto que inactivo. Utiliza mucha menos energía total para producir el resultado. (Duración de la batería / calor / calentamiento global).
Esto es tan rápido que probablemente no desee escribirlo en el disco. Simplemente vuelva a generar según sea necesario (de la misma semilla si desea los mismos datos nuevamente). Incluso si desea alimentarlo a un proceso de subprocesos múltiples que puede usar todas las CPU, ejecutar esto para canalizar los datos lo dejará caliente en el caché L3 (y el caché L2 en el núcleo que lo escribió), y lo usará muy Poco tiempo de CPU. (Pero tenga en cuenta que las tuberías agregan una gran cantidad de gastos generales en lugar de escribir en
/dev/null
. En un Skylake i7-6700k, las tuberíaswc -c
u otro programa que solo lee + descarta su entrada, es aproximadamente 8 veces más lento que escribir en/dev/null
, y solo usa el 70% de un CPU. Pero eso sigue siendo 4.0GB / s en una CPU de 3.9GHz.Regenerarlo es más rápido que volver a leerlo incluso desde un SSD rápido conectado a PCIe, pero IDK si es más eficiente en energía (el multiplicador de enteros vectoriales se mantiene bastante ocupado, y probablemente consuma mucha energía, junto con otros AVX2 256 ALU de vector). OTOH, no sé cuánto tiempo de CPU tomaría la lectura del disco de algo que estaba maximizando todos los núcleos que procesan esta entrada. Supongo que un cambio de contexto para volver a generar en trozos de 128k podría ser competitivo con la ejecución de código de sistema de archivos / caché de página y la asignación de páginas para leer datos del disco. Por supuesto, si ya está caliente en el caché de página, es básicamente memcpy. ¡OTOH, ya escribimos sobre tan rápido como memcpy! (que tiene que dividir el ancho de banda de la memoria principal entre lectura y escritura). (También tenga en cuenta que escribir en la memoria que '
rep movsb
(memcpy y memset optimizados en microcódigo, lo que evita RFO, desde la implementación de Andy Glew en P6 (Pentium Pro) )).Hasta ahora, esto es solo una prueba de concepto, y el manejo de la nueva línea es aproximadamente correcto. Está mal alrededor de los extremos de un búfer de potencia de 2. Con más tiempo de desarrollo. Estoy seguro de que podría encontrar una forma más eficiente de insertar nuevas líneas que también sea exactamente correcta, con una sobrecarga al menos tan baja como esta (en comparación con la salida de solo espacios). Creo que esto es algo así como del 10 al 20%. Solo estoy interesado en saber qué tan rápido podríamos hacer que esto funcione, no en tener una versión pulida, por lo que dejaré esa parte como un ejercicio para el lector, con comentarios que describen algunas ideas.
En un Haswell i5 con su turbo máximo de 2.5GHz, con DDR3-1600MHz RAM , cronometrado produciendo 100GiB pero reducido. (Programado en cygwin64 en Win10 con gcc5.4
-O3 -march=native
, omitido-funroll-loops
ya que estaba teniendo dificultades para obtener tiempos decentes en esta computadora portátil prestada. Debería haber arrancado Linux en un USB).escribiendo a / dev / null a menos que se especifique lo contrario.
wc -c
Cygwin (AVX2) , con un tamaño de búfer de 128 kB: 0,32 s con CPU a 2,38 GHz (turbo máximo de doble núcleo). (tiempos sin escala: real = 32.466s usuario = 11.468s sys = 41.092s, incluidos ambos ywc
). Sin embargo, solo la mitad de los datos se copiaron realmente, porque mi tonto programa supone que la escritura hace el búfer completo, aunque ese no es el caso y cygwin write () solo hace 64k por llamada en una tubería.Entonces, con SSE2, esto es aproximadamente 15 veces más rápido que el código escalar de @Nominal Animal. Con AVX2, es aproximadamente 30 veces más rápido. No probé una versión del código de Nominal que solo usa en
write()
lugar defwrite()
, pero presumiblemente para buffers grandes, stdio generalmente se mantiene fuera del camino. Si está copiando los datos, eso explicaría mucha desaceleración.Tiempos para producir 1GB de datos en un Core2Duo E6600 (Merom 2.4GHz, 32kiB privado L1, 4MiB cachés L2 compartidos), DDR2-533MHz en Linux 4.2 de 64 bits (Ubuntu 15.10). Aún utilizando un tamaño de búfer de 128 kB para write (), no he explorado esa dimensión.
escribiendo a / dev / null a menos que se especifique lo contrario.
wc -c
: 0.593s (sin escala : real = 59.266s usuario = 20.148s sys = 1m6.548s, incluido el tiempo de CPU de wc). El mismo número de llamadas al sistema write () que con cygwin, pero en realidad canaliza todos los datos porque Linux maneja los 128k de write () en una tubería.fwrite()
versión (gcc5.2-O3 -march=native
), ejecuta con./decdig 100 $((1024*1024*1024/200)) > /dev/null
: 3.19s +/- 0,1%, con 1,40 instrucción por ciclo. -funroll-loops hizo tal vez una pequeña diferencia.clang-3.8 -O3 -march=native
: 3.42s +/- 0.1%fwrite
Tubería nominal awc -c
: real = 3.980s usuario = 3.176s sys = 2.080sclang++-3.8 -O3 -march=native
): 22.885s +/- 0.07%, con 0.84 instrucciones por ciclo. (g ++ 5.2 fue ligeramente más lento: 22.98s). Escribir solo una línea a la vez probablemente duele significativamente.tr < /dev/urandom | ...
: real = 41.430s usuario = 26.832s sys = 40.120s.tr
estaba obteniendo todo el núcleo de la CPU para sí mismo la mayor parte del tiempo, pasando casi todo su tiempo en el controlador del núcleo generando bytes aleatorios y copiándolos en una tubería. El otro núcleo en esta máquina de doble núcleo estaba ejecutando el resto de la tubería.time LC_ALL=C head -c512M </dev/urandom >/dev/null
: es decir, solo leer tanta aleatoriedad sin tuberías: real = 35.018s usuario = 0.036s sys = 34.940s.LANG=en_CA.UTF-8
:: real = 4m32.634s usuario = 4m3.288s sys = 0m29.364.LC_ALL=C LANG=C
: real = 4m18.637s usuario = 3m50.324s sys = 0m29.356s. Aún muy lento.dig3 = v%10
paso es sobre el punto de equilibrio en este HW): 0.166s (1.82 instrucciones por ciclo) . Este es básicamente el límite inferior para lo que podemos acercarnos con un manejo de nueva línea perfectamente eficiente.v%10
, 0.222 segundos +/- 0.4%, 2.12 instrucciones por ciclo. (Compilado con gcc5.2,.-march=native -O3 -funroll-loops
Desenrollar bucles sí ayuda para este código en este hardware. No lo use a ciegas, especialmente para programas grandes).Cómo está hecho
Un PRNG rápido es obviamente esencial. xorshift128 + se puede vectorizar, por lo que tiene dos o cuatro generadores de 64 bits en paralelo, en elementos de un vector SIMD. Cada paso produce un vector completo de bytes aleatorios. ( Implementación de 256b AVX2 aquí con intrínsecos Intel ). Lo elegí por la elección de xorshift * de Nominal, porque la multiplicación de enteros vectoriales de 64 bits solo es posible en SSE2 / AVX2 con técnicas de precisión extendida .
Dado un vector de bytes aleatorios, podemos dividir cada elemento de 16 bits en múltiples dígitos decimales. Producimos múltiples vectores de elementos de 16 bits que son cada uno un dígito ASCII + espacio ASCII . Lo almacenamos directamente en nuestro búfer de salida.
Mi versión original solo solía
x / 6554
obtener un dígito aleatorio de cada elemento uint16_t de un vector. Siempre está entre 0 y 9, inclusive. Está sesgado9
, porque(2^16 -1 ) / 6554
es solo 9.99923. (6554 = ceil ((2 ^ 16-1) / 10), lo que garantiza que el cociente sea siempre <10.)x/6554
se puede calcular con una multiplicación por una constante "mágica" ( el recíproco de punto fijo ) y un desplazamiento a la derecha del resultado de la mitad superior. Este es el mejor caso para la división por una constante; algunos divisores realizan más operaciones, y la división firmada requiere trabajo adicional.x % 10
tiene un sesgo similar y no es tan barato de calcular. (la salida asm de gcc es equivalente ax - 10*(x/10)
, es decir, una multiplicación y resta adicionales en la parte superior de la división usando un inverso multiplicativo modular.) Además, el bit más bajo de xorshift128 + no es de alta calidad , por lo que es mejor dividir para tomar entropía de bits altos ( para calidad y velocidad) que el módulo para tomar entropía de bits bajos.Sin embargo, podemos usar más de la entropía en cada uint16_t mirando los dígitos decimales bajos, como la
digit()
función de @ Nominal . Para obtener el máximo rendimiento, decidí tomar los 3 dígitos decimales bajos yx/6554
, para guardar un PMULLW y PSUBW (y probablemente algunos MOVDQA) frente a la opción de mayor calidad de tomar los 4 dígitos decimales bajos. x / 6554 se ve ligeramente afectado por los 3 dígitos decimales bajos, por lo que existe cierta correlación entre los dígitos del mismo elemento (separación de 8 o 16 dígitos en la salida ASCII, dependiendo del ancho del vector).Creo que gcc se divide por 100 y por 1000, en lugar de una cadena más larga que se divide sucesivamente por 10, por lo que probablemente no esté acortando significativamente la longitud de la cadena de dependencia no transportada en bucle que produce 4 resultados de cada salida PRNG. port0 (multiplicación y cambio de vectores) es el cuello de botella debido a las inversas multiplicativas modulares y los cambios en xorshift +, por lo que definitivamente es útil guardar una multiplicación de vectores.
xorshift + es tan rápido que incluso usar solo ~ 3.3 bits de aleatoriedad de cada 16 (es decir, 20% de eficiencia) no es mucho más lento que dividirlo en varios dígitos decimales. Solo aproximamos la distribución uniforme, porque esta respuesta se centra en la velocidad siempre que la calidad no sea tan mala.
Cualquier tipo de comportamiento condicional que mantenga un número variable de elementos requeriría mucho más trabajo. (Pero tal vez podría hacerse de manera algo eficiente usando las técnicas de empaquetado a la izquierda SIMD . Sin embargo, eso se vuelve menos eficiente para tamaños de elementos pequeños; las tablas de búsqueda de máscara aleatoria gigante no son viables, y no hay una mezcla aleatoria de cruce de carriles AVX2 con menos de 32- elementos de bit. Una versión de 128b PSHUFB aún podría generar una máscara sobre la marcha con BMI2 PEXT / PDEP, como puede hacerlo con AVX2 con elementos más grandes , pero es complicado porque un entero de 64 bits solo contiene 8 bytes. en esa respuesta tiene algún código que podría funcionar para conteos de elementos más altos).
Si la latencia del RNG es un cuello de botella, podríamos ir aún más rápido ejecutando dos vectores de generadores en paralelo, alternando cuál usamos. El compilador aún puede mantener fácilmente todo en registros en un bucle desenrollado, y eso permite que las dos cadenas de dependencia se ejecuten en paralelo.
En la versión actual, cortando la salida del PRNG, realmente tenemos un cuello de botella en el rendimiento del puerto 0, no en la latencia PRNG, por lo que no hay necesidad de eso.
El código: versión AVX2
Versión completa con más comentarios sobre el explorador del compilador Godbolt .
No muy ordenado, lo siento, tengo que dormir y quiero publicar esto.
Para obtener la versión SSE2,
s/_mm256/_mm
,s/256/128/
,s/v16u/v8u/
, y el cambiovector_size(32)
a 16. También cambiar el incremento de nueva línea de 4 x 16 a 4 * 8. (Como dije, el código es desordenado y no está bien configurado para compilar dos versiones. Originalmente no planeaba hacer una versión AVX2, pero realmente quería probar en una CPU Haswell a la que tenía acceso).Compile con gcc, clang o ICC (o con suerte cualquier otro compilador que comprenda el dialecto GNU C de C99 y los intrínsecos de Intel). Las extensiones de vector GNU C son muy convenientes para que el compilador genere los números mágicos para la división / módulo utilizando inversos multiplicativos modulares, y ocasionalmente
__attribute__
s son útiles.Esto podría escribirse de forma portátil, pero tomaría más código.
Notas de rendimiento:
La tienda superpuesta para insertar nuevas líneas tiene una sobrecarga considerable para decidir dónde colocarla (predicciones erróneas de sucursales y cuellos de botella frontend en Core2), pero la tienda en sí no tiene ningún impacto en el rendimiento. Al comentar solo esa instrucción de tienda en el compilador del compilador (dejando todas las ramificaciones iguales), el rendimiento en Core2 no cambió por completo, y las ejecuciones repetidas dieron el mismo tiempo a +/- menos del 1%. Así que concluyo que el búfer / caché de la tienda lo maneja bien.
Aún así, usar algún tipo de ventana giratoria
ascii_digitspace
con un elemento que tenga una nueva línea podría ser aún más rápido, si desenrollamos lo suficiente como para que desaparezcan los contadores / ramificaciones.Escribir en / dev / null es básicamente un no-op, por lo que el búfer probablemente se mantiene caliente en la caché L2 (256 kB por núcleo en Haswell). Se espera una aceleración perfecta de 128b a 256b: no hay instrucciones adicionales y todo (incluidas las tiendas) ocurre con el doble de ancho. Sin embargo, la rama de inserción de nueva línea se toma el doble de veces. Desafortunadamente, no tuve tiempo en mi configuración de Haswell cygwin con esa parte
#ifdef
editada.2.5GHz * 32B / 13.7GB / s = 5.84 ciclos por AVX2-store en Haswell. Eso es bastante bueno, pero podría ser más rápido. Quizás haya algo de sobrecarga en las llamadas al sistema cygwin de lo que pensaba. No intenté comentarlos en la salida asm del compilador (lo que garantizaría que nada se optimice).
La memoria caché L1 puede mantener una tienda de 32B por reloj, y L2 no tiene un ancho de banda mucho menor (sin embargo, una latencia más alta).
Cuando miré a IACA hace algunas versiones (sin la ramificación de las nuevas líneas, pero solo obtenía un vector ASCII por vector RNG), estaba prediciendo algo así como una tienda de vectores 32B por 4 o 5 relojes.
Esperaba obtener una mayor velocidad al extraer más datos de cada resultado de RNG, basándome en mirar el asm, considerando las guías de Agner Fog y otros recursos de optimización para los que he agregado enlaces en el wiki de etiquetas SO x86 .
Probablemente sería significativamente más rápido en Skylake , donde la multiplicación y el desplazamiento de enteros vectoriales pueden ejecutarse en el doble de puertos (p0 / p1) en comparación con Haswell (solo p0). xorshift y la extracción de dígitos utilizan muchos cambios y multiplicaciones. ( Actualización: Skylake lo ejecuta a 3.02 IPC, dándonos 3.77 ciclos por tienda AVX2 de 32 bytes , cronometrado a 0.030s por iteración de 1GB, escribiendo
/dev/null
en Linux 4.15 en i7-6700k a 3.9GHz.No requiere el modo de 64 bits para funcionar bien . La versión SSE2 es igual de rápida cuando se compila
-m32
, porque no necesita muchos registros de vectores, y toda la matemática de 64 bits se realiza en vectores, no en registros de propósito general.En realidad, es un poco más rápido en modo de 32 bits en Core2, porque la macro fusión de comparación / ramificación solo funciona en modo de 32 bits, por lo que hay menos uops para el núcleo fuera de orden (18.3s (1.85 instrucciones por reloj) vs 16.9s (2.0 IPC)). El tamaño de código más pequeño por no tener prefijos REX también ayuda a los decodificadores de Core2.
Además, algunos movimientos de vector reg-reg se reemplazan con cargas, ya que ya no se fijan todas las constantes en los registros vectoriales. Dado que el rendimiento de carga del caché L1 no es un cuello de botella, esto realmente ayuda. (por ejemplo, multiplicar por un vector constante de
set1(10)
:movdqa xmm0, xmm10
/ sepmullw xmm0, xmm1
convierte enmovdqa xmm0, [constant]
/pmullw xmm0, xmm1
.) Dado que MOVDQA reg-reg requiere un puerto ALU, compite con el trabajo real que se realiza, pero una carga MOVDQA solo compite por el ancho de banda de decodificación front-end. (Tener una dirección de 4 bytes dentro de muchas instrucciones cancela gran parte de la ganancia al guardar los prefijos REX.No me sorprendería si salvando ALU MOVDQA uops es de donde provienen las ganancias reales, ya que la interfaz debería mantenerse bastante bien con el promedio de 2.0 IPC.
Todas estas diferencias desaparecen en Haswell, donde todo debería ejecutarse desde la memoria caché decodificada-uop, si no el búfer de bucle invertido. La macro fusión de rama ALU + funciona en ambos modos desde Nehalem.
fuente
Aquí hay una solución que espero que sea fácil de entender:
od
crea una secuencia uniforme de dígitos hexadecimales de/dev/random
.tr
se deshace de las letras, solo mantiene los0-9
dígitosfold
asegura que haya 100 dígitos por líneaawk
inserta espacios dentro de las líneashead
trunca la entrada a 1 gigabytefuente
Puede usar el
jot
comando para esto:fuente
fmt
no tiene una opción de ancho de objetivo. De todos modos, ¡será exacto porque todos los dígitos ocupan exactamente una columna!fmt
versión esfmt (GNU coreutils) 8.25
(Ubuntu 16.04)536870912
Esto es similar al método de Stéphane Chazelas, sin embargo, leí 64 bits a la vez para mejorar el rendimiento. La distribución sigue siendo uniforme, pero ahora obtienes 19 dígitos por cada 8 bytes en lugar de solo 8 en el mejor caso como antes
En la plataforma de 32 bits, se leerán 9 dígitos cada vez en lugar de 19.
fuente
perl
no está compilado con soporte cuádruple.next if $n >= 1000000000; $s = sprintf("%09u", $n);
para obtener solo 9 dígitos$n = unpack("Q")
si quad no es compatible.BEGIN{$/=\4; $,=" "} $n = unpack("L");
también<16e18
y divide entre 16, obtiene 18 dígitos 86.7% para 1.95 dpB. Con 32 bits,<4e9 /4
obtiene 9 dígitos 93.1% para 2.10 dpB. Pero 5 bytes (como hexadecimal (H10))<1e12
da 12 dígitos 90.9% para 2.18 dpB, o dividir el hexadecimal por la mitad y hacer cada mitad<1e6
da 6 dígitos 95.4% para 2.29 dpB; esto se acerca al límite de log_10 (256) = 2.41.Estoy de acuerdo con Nominal Animal en el uso de un lenguaje de programación compilado si necesita velocidad. Sin embargo, no tiene que escribir su propio código RNG en C. C ++ 11 ofrece el excelente Mersenne Twister como parte de su biblioteca estándar.
El código anterior es razonablemente simple y toma aproximadamente un minuto cuando canalizo la salida a un archivo. Podemos ir mucho más rápido creando una cadena lo suficientemente grande para 100 dígitos y hackeando los dígitos. Esto nos permite llamar a cout cada línea en lugar de cada dígito.
Este código toma mi máquina alrededor de seis segundos. Recuerde que es una salida estándar, así que canalícela a un archivo.
Tengo un par de renuncias. Primero, estoy escribiendo esto en una PC con Windows. Creo que todas las bibliotecas están presentes en Linux, pero si me equivoco, asegúrese de señalarlo.
Además, en realidad genera exactamente 500 millones de dígitos separados por espacios, que técnicamente es un gigabyte, pero tal vez no sea exactamente lo que deseaba. Produce 5 millones de líneas, 100 dígitos por línea. Si la diferencia es importante, puede aumentar el número de líneas. En mi cuadro de Windows, el archivo parece ser un poco más grande que 10 ^ 9 bytes, lo que creo que tiene algo que ver con los caracteres adicionales de nueva línea.
fuente
/dev/null
que sería mucho más rápido que escribir en un archivo realwrite()
llamada de sistema grande es una memoria en el caché de página, que se bloquea solo si el núcleo decide hacer eso en lugar de asignar más espacio de búfer. Este programa solo debe tener un cuello de botella en la E / S del disco cuando la memoria es escasa, o si ha utilizado O_DIRECT para omitir el caché de página. Siwrite()
en trozos más pequeños que el tamaño de caché, es de esperar que sus datos solo vayan a la memoria principal una vez, y el búfer que se reescribe en su lugar permanece caliente en el caché L2 o L3.Depende de su definición de "aleatorio". Si te refieres a criptográficamente al azar, solo tienes que conseguir una buena biblioteca y morder la bala, esperar a que se ejecute.
Si solo necesita algo que parezca bastante aleatorio, esta es una manera fácil:
Puede tardar una hora en ejecutarse en una máquina lenta; lo suficientemente rápido y aleatorio para la mayoría de los propósitos.
fuente
/dev/urandom
es probable que sea mejor quegzip
, tanto en velocidad como en aleatoriedad.Get a file that is several Gb long
necesitará un archivo ** de al menos 8 Gb` para obtener un archivo de 1 GBfuente
cat file | tr
cuando puedes simplementetr <file
. IIRC, puedes incluso<file tr
. Pensé que solo estabas hablando de que este script de shell se ve torpe y lento, comodu | awk
después de cada línea para verificar el tamaño, y volver a abrir el archivo para agregar cada línea en lugar de redirigir fuera del bucle.cat /dev/urandom | busy-cmd
es uno de esos casos raros en los que puede tener sentido, ya que puede dividir la generación aleatoria y el cmd ocupado entre procesadores. No tanto para tr, pero hace una diferencia para Sam,od
por ejemplo.