У меня есть файл размером 100 Мб, который помещается в ОЗУ на системе GNU/Linux.
Это довольно медленно:
sort bigfile > bigfile.sorted
и не использует все 48 ядер на моем компьютере.
Как быстро отсортировать этот файл?
решение1
Предположим, у вас 48 ядер, 500 ГБ свободной оперативной памяти, а файл имеет размер 100 млн строк и помещается в память.
Если использовать обычную сортировку, то она довольно медленная:
$ time sort bigfile > bigfile.sort
real 4m48.664s
user 21m15.259s
sys 0m42.184s
Вы можете сделать это немного быстрее, игнорируя свою локаль:
$ export LC_ALL=C
$ time sort bigfile > bigfile.sort
real 1m51.957s
user 6m2.053s
sys 0m42.524s
Вы можете ускорить процесс, указав sort использовать больше ядер:
$ export LC_ALL=C
$ time sort --parallel=48 bigfile > bigfile.sort
real 1m39.977s
user 15m32.202s
sys 1m1.336s
Вы также можете попробовать выделить sort больше рабочей памяти (это не поможет, если у sort уже достаточно памяти):
$ export LC_ALL=C
$ time sort --buffer-size=80% --parallel=48 bigfile > bigfile.sort
real 1m39.779s
user 14m31.033s
sys 1m0.304s
Но, похоже, sort действительно любит делать много однопоточности. Вы можете заставить его больше распараллеливать с помощью:
$ 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
Он нарезает файл на 48 блоков на лету (по одному блоку на ядро), сортирует эти блоки параллельно. Затем мы делаем сортировку слиянием пары из них. Затем мы делаем сортировку слиянием пары из них. Затем мы делаем сортировку слиянием пары из них. Затем мы делаем сортировку слиянием пары из них. Затем мы делаем сортировку слиянием пары из них. И так далее, пока у нас не останется только один вход. Все это выполняется параллельно, когда это возможно.
Для файла размером 100 ГБ с 4 G строками тайминги следующие:
$ 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
Таким образом, использование распараллеливания увеличивает скорость примерно в 4 раза.
Чтобы упростить использование, я превратил его в небольшой инструмент, parsort
который теперь является частью GNU Parallel.
Он также поддерживает sort
параметры и чтение из stdin ( parsort -k2rn < bigfile
).