Tengo un archivo de 100 M de líneas que cabe en la RAM en un sistema GNU/Linux.
Esto es bastante lento:
sort bigfile > bigfile.sorted
y no utiliza los 48 núcleos de mi máquina.
¿Cómo clasifico ese archivo rápidamente?
Respuesta1
Supongamos que tiene 48 núcleos, 500 GB de RAM libre y el archivo tiene 100 M de líneas y cabe en la memoria.
Si usa la clasificación normal, es bastante lento:
$ time sort bigfile > bigfile.sort
real 4m48.664s
user 21m15.259s
sys 0m42.184s
Puedes hacerlo un poco más rápido ignorando tu ubicación:
$ export LC_ALL=C
$ time sort bigfile > bigfile.sort
real 1m51.957s
user 6m2.053s
sys 0m42.524s
Puedes hacerlo más rápido diciéndole a sort que use más núcleos:
$ export LC_ALL=C
$ time sort --parallel=48 bigfile > bigfile.sort
real 1m39.977s
user 15m32.202s
sys 1m1.336s
También puedes intentar darle más memoria de trabajo a la clasificación (esto no ayuda si la clasificación ya tiene suficiente memoria):
$ export LC_ALL=C
$ time sort --buffer-size=80% --parallel=48 bigfile > bigfile.sort
real 1m39.779s
user 14m31.033s
sys 1m0.304s
Pero parece que a este tipo realmente le gusta hacer muchos subprocesos únicos. Puedes forzarlo a que se paralelice más con:
$ merge() {
if [ $1 -le 1 ] ; then
parallel -Xj1 -n2 --dr 'sort -m <({=uq=}) | mbuffer -m 30M;'
else
parallel -Xj1 -n2 --dr 'sort -m <({=uq=}) | mbuffer -m 30M;' |
merge $(( $1/2 ));
fi
}
# Generate commands that will read blocks of bigfile and sort those
# This only builds the command - it does not run anything
$ parallel --pipepart -a bigfile --block -1 --dr -vv sort |
# Merge these commands 2 by 2 until only one is left
# This only builds the command - it does not run anything
merge $(parallel --number-of-threads) |
# Execute the command
# This runs the command built in the previous step
bash > bigfile.sort
real 0m30.906s
user 0m21.963s
sys 0m28.870s
Corta el archivo en 48 bloques sobre la marcha (un bloque por núcleo) y los clasifica en paralelo. Luego hacemos una especie de fusión de un par de esos. Luego hacemos una especie de fusión de un par de esos. Luego hacemos una especie de fusión de un par de esos. Luego hacemos una especie de fusión de un par de esos. Luego hacemos una especie de fusión de un par de esos. Y así sucesivamente, hasta que solo tengamos una entrada. Todo esto se hace en paralelo cuando sea posible.
Para un archivo de 100 GB con líneas 4 G, los tiempos son:
$ LC_ALL=C time sort --parallel=48 -S 80% --compress-program pzstd bigfile >/dev/null
real 77m22.255s
$ LC_ALL=C time parsort bigfile >/dev/null
649.49user 727.04system 18:10.37elapsed 126%CPU (0avgtext+0avgdata 32896maxresident)k
Entonces, usar la paralelización acelera alrededor de un factor de 4.
Para hacerlo más fácil de usar, lo he convertido en una pequeña herramienta: parsort
que ahora forma parte de GNU Parallel.
También admite sort
opciones y lectura desde stdin ( parsort -k2rn < bigfile
).