Почему coreutils сортируется медленнее, чем Python?

20

Я написал следующий скрипт для проверки скорости сортировки Python:

from sys import stdin, stdout
lines = list(stdin)
lines.sort()
stdout.writelines(lines)

Затем я сравнил это с командой coreutils sortдля файла, содержащего 10 миллионов строк:

$ time python sort.py <numbers.txt >s1.txt
real    0m16.707s
user    0m16.288s
sys     0m0.420s

$ time sort <numbers.txt >s2.txt 
real    0m45.141s
user    2m28.304s
sys     0m0.380s

Встроенная команда использовала все четыре процессора (Python использовал только один), но для ее запуска потребовалось в 3 раза больше времени! Что дает?

Я использую Ubuntu 12.04.5 (32-разрядная версия), Python 2.7.3 и sort8.13

augurar
источник
@goldilocks Да, посмотрите на пользователя в режиме реального времени.
Авгурар
Да, ты прав. Видимо это было распараллелено в coreutils 8.6.
Златовласка
Можете ли вы использовать, --buffer-sizeчтобы указать, что sortиспользовать всю доступную физическую память и посмотреть, помогает ли это?
iruvar
@ 1_CR Пробовал с 1 ГБ буфера, без существенных изменений во времени. Он использовал только около 0,6 ГБ, поэтому дальнейшее увеличение размера буфера не помогло бы.
Авгурар

Ответы:

22

Комментарий Изкаты выявил ответ: сравнение с конкретной локалью . Команда sortиспользует локаль, указанную средой, тогда как Python по умолчанию сравнивает порядок байтов. Сравнение строк UTF-8 сложнее, чем сравнение строк байтов.

$ time (LC_ALL=C sort <numbers.txt >s2.txt)
real    0m5.485s
user    0m14.028s
sys     0m0.404s

Как насчет этого.

augurar
источник
И как они сравниваются для строк UTF-8?
Жиль "ТАК - перестань быть злым"
@Gilles Модифицируя скрипт Python, чтобы использовать его locale.strxfrmдля сортировки, скрипт занял ~ 32 секунды, все еще быстрее, sortно гораздо меньше.
Авгурар
3
Python 2.7.3 выполняет сравнение байтов, но Python3 будет выполнять сравнение слов в юникоде. Python3.3 примерно вдвое медленнее Python2.7 для этого «теста». Накладные расходы на упаковку необработанных байтов в представление Unicode даже выше, чем уже значимые объекты упаковки, которые должен делать Python 2.7.3.
Антон
2
Я нашел такое же замедление cut, как и другие. На нескольких машинах я теперь export LC_ALL=Cв .bashrc. Но будьте осторожны: это существенно ломает wc(кроме wc -l), просто чтобы назвать пример. «Плохие байты» вообще не учитываются ...
Уолтер Тросс
1
Эта проблема с производительностью также возникает с grep: вы можете получить существенное улучшение производительности при сбрасывании больших файлов, отключив UTF-8, особенно при выполненииgrep -i
Адриан Пронк
7

Это скорее дополнительный анализ, чем фактический ответ, но, похоже, он варьируется в зависимости от сортируемых данных. Во-первых, базовое чтение:

$ printf "%s\n" {1..1000000} > numbers.txt

$ time python sort.py <numbers.txt >s1.txt
real    0m0.521s
user    0m0.216s
sys     0m0.100s

$ time sort <numbers.txt >s2.txt
real    0m3.708s
user    0m4.908s
sys     0m0.156s

ОК, Python намного быстрее. Тем не менее, вы можете сделать coreutils sortбыстрее, сказав, что он сортирует по численности:

$ time sort <numbers.txt >s2.txt 
real    0m3.743s
user    0m4.964s
sys     0m0.148s

$ time sort -n <numbers.txt >s2.txt 
real    0m0.733s
user    0m0.836s
sys     0m0.100s

Это намного быстрее, но питон все еще выигрывает с большим отрывом. Теперь давайте попробуем еще раз, но с несортированным списком из 1М номеров:

$ sort -R numbers.txt > randomized.txt

$ time sort -n <randomized.txt >s2.txt 
real    0m1.493s
user    0m1.920s
sys     0m0.116s

$ time python sort.py <randomized.txt >s1.txt
real    0m2.652s
user    0m1.988s
sys     0m0.064s

Coreutils sort -nбыстрее для несортированных числовых данных (хотя вы можете изменить параметр сортировки Python, cmpчтобы сделать его быстрее). Coreutils sortвсе еще значительно медленнее без -nфлага. Итак, что насчет случайных символов, а не чистых чисел?

$ tr -dc 'A-Za-z0-9' </dev/urandom | head -c1000000 | 
    sed 's/./&\n/g' > random.txt

$ time sort  <random.txt >s2.txt 
real    0m2.487s
user    0m3.480s
sys     0m0.128s

$ time python sort.py  <random.txt >s2.txt 
real    0m1.314s
user    0m0.744s
sys     0m0.068s

Python по-прежнему превосходит coreutils, но с гораздо меньшим отрывом, чем то, что вы показываете в своем вопросе. Удивительно, но это все еще быстрее, если смотреть на чисто алфавитные данные:

$ tr -dc 'A-Za-z' </dev/urandom | head -c1000000 |
    sed 's/./&\n/g' > letters.txt

$ time sort   <letters.txt >s2.txt 
real    0m2.561s
user    0m3.684s
sys     0m0.100s

$ time python sort.py <letters.txt >s1.txt
real    0m1.297s
user    0m0.744s
sys     0m0.064s

Также важно отметить, что эти два не производят одинаковый отсортированный вывод:

$ echo -e "A\nB\na\nb\n-" | sort -n
-
a
A
b
B

$ echo -e "A\nB\na\nb\n-" | python sort.py 
-
A
B
a
b

Как ни странно, эта --buffer-sizeопция, казалось, не имела большого (или какого-либо) значения в моих тестах. В заключение, предположительно из-за различных алгоритмов, упомянутых в ответе Златовласки, python sortв большинстве случаев выглядит быстрее, но числовой GNU sortпревосходит его по несортированным числам 1 .


ОП, вероятно, нашел основную причину, но для полноты изложения приведу окончательное сравнение:

$ time LC_ALL=C sort   <letters.txt >s2.txt 
real    0m0.280s
user    0m0.512s
sys     0m0.084s


$ time LC_ALL=C python sort.py   <letters.txt >s2.txt 
real    0m0.493s
user    0m0.448s
sys     0m0.044s

1 Тот, у кого больше python-fu, чем я, должен попытаться проверить настройку, list.sort()чтобы увидеть ту же скорость, может быть достигнут путем указания метода сортировки.

Тердон
источник
5
У сортировки Python есть дополнительное преимущество в скорости, основанное на вашем последнем примере: числовой порядок ASCII. sortкажется, делает немного дополнительной работы для сравнения прописных / строчных букв.
Изката
@Izkata Вот и все! Смотрите мой ответ ниже.
Авгурар
1
На самом деле у python немало накладных расходов на создание своих внутренних строк из необработанного stdinввода. Преобразование тех чисел ( lines = map(int, list(stdin))) и обратно ( stdout.writelines(map(str,lines))) делает всю сортировку идти медленнее, вверх от 0.234s реально 0.720s на моей машине.
Антон
6

Обе реализации находятся на C, так что ровного игрового поля нет. Coreutils, sort очевидно, использует алгоритм слияния . Mergesort выполняет фиксированное число сравнений, которое логарифмически увеличивается до размера ввода, то есть большое O (n log n).

Сортировка Python использует уникальную гибридную сортировку слиянием / вставкой, timsort , которая будет выполнять различное число сравнений с наилучшим сценарием O (n) - предположительно, в уже отсортированном списке - но, как правило, логарифмическая (логически вы не может быть лучше, чем логарифмическая для общего случая при сортировке).

Учитывая два различных логарифмических сорта, один может иметь преимущество перед другим в некотором конкретном наборе данных. Традиционная сортировка слиянием не меняется, поэтому она будет работать одинаково независимо от данных, но, например, быстрая сортировка (также логарифмическая), которая меняется, будет лучше работать с некоторыми данными, но хуже с другими.

Фактор три (или больше 3, так sortкак распараллеливается) довольно немного, что заставляет меня задуматься, нет ли здесь каких-то непредвиденных обстоятельств, таких как sortпереключение на диск ( -Tвариант может показаться, что это так). Тем не менее, ваша низкая система против времени пользователя означает, что это не проблема.

лютик золотистый
источник
Хорошо, что обе реализации написаны на C. Я уверен, что если бы я реализовал алгоритм сортировки в Python, он был бы намного, намного медленнее.
Авгурар
Кстати, файл состоит из случайно сгенерированных значений с плавающей точкой от 0 до 1, поэтому не должно быть слишком большой структуры для использования.
Авгурар