Чому 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 використовувала лише один), але запустила приблизно три рази! Що дає?

Я використовую Ubuntu 12.04.5 (32-розрядні), Python 2.7.3 та sort8.13


@goldilocks Так, подивіться на користувача проти реального часу.
augurar

Ага - ти маєш рацію. Мабуть, це було паралельне в ядрах 8.6.
goldilocks

Чи можете ви --buffer-sizeвказати, що sortвикористовувати всю наявну фізичну пам'ять, і подивитися, чи це допомагає?
iruvar

@ 1_CR Спробував це з буфером 1 Гб, суттєвих змін у часі немає. Він використовував лише близько 6 ГБ, тому збільшення розміру буфера далі не допоможе.
Авгур

Відповіді:


22

Коментар Ізката виявив відповідь: порівняння, що стосується конкретних місцевостей. sortКоманда використовує локаль , зазначену навколишнє середовище, в той час як по замовчуванням Python для порівняння порядку проходження байтів. Порівнювати рядки UTF-8 важче, ніж порівнювати байтові рядки.

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

Як про те, що.


І як вони порівнюють струни UTF-8?
Жил 'SO- перестань бути злим'

@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

Добре, пітон набагато швидший. Однак ви можете зробити 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варіант, схоже, не міг значно (або будь-яку) різницю в моїх тестах. На закінчення, мабуть, через різні алгоритми, згадані у відповіді Голділок, 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 Хтось, хто має більше пітон-фу, ніж я, повинен спробувати перевірити налаштування, list.sort()щоб побачити однакову швидкість, можна досягти, вказавши метод сортування.


5
Сорт Python має додаткову перевагу швидкості на основі вашого останнього зразка: числового порядку ASCII. sortЗдається, роблять трохи додаткової роботи для порівняння великих / малих літер.
Ізката

@Izkata Це все! Дивіться мою відповідь нижче.
аугурар

1
Насправді python має досить великі накладні витрати, створюючи свої внутрішні рядки із вихідних stdinданих. Перетворення їх у числа (lines = map(int, list(stdin)) ) і назад ( stdout.writelines(map(str,lines))) робить все сортування повільнішим - від 0,234 до реального до 0,720 на моїй машині.
Антон

6

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

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

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

Коефіцієнт три (або більше 3, оскільки sort є паралельним), хоч і небагато, що змушує мене замислитись, чи немає тут якихось надзвичайних ситуацій, наприклад, sortзаміни на диск ( -Tваріант, мабуть, передбачає, що це робить). Тим не менш, ваш низький сис проти часу користувача означає, що це не проблема.


Добре, що обидві реалізації написані в C. Я впевнений, що якби я реалізував алгоритм сортування в Python, це було б значно, набагато повільніше.
Авгура

До речі, файл складається з випадково згенерованих плаваючих значень між 0 і 1, тому не повинно бути занадто багато структури для використання.
Авгур
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.