web-dev-qa-db-ja.com

coreutilsのソートがPythonより遅いのはなぜですか?

Pythonのソート機能の速度をテストするために、次のスクリプトを書きました。

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

次に、これを1000万行を含むファイルのcoreutils sortコマンドと比較しました。

$ 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

組み込みコマンドは4つのCPUすべてを使用しましたが(Pythonは1つしか使用しませんでした)、実行に約3倍の時間がかかりました!何ができますか?

Ubuntu 12.04.5(32ビット)、Python 2.7.3、およびsort 8.13を使用しています。

21
augurar

Izkataのコメント 答えを明らかにしました:ロケール固有の比較。 sortコマンドは環境によって指定されたロケールを使用しますが、Pythonはデフォルトでバイトオーダー比較になります。UTF-8文字列の比較は、バイト文字列の比較より困難です。

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

どのようにそのことについて。

22
augurar

どちらの実装もCで行われているため、そこには平等な競争条件があります。 Coreutils sortどうやらmergesort アルゴリズムを使用します。 Mergesortは固定数の比較を行い、入力サイズに対数的に増加します。つまり、 big O (n log n)です。

Pythonのソートは、一意のハイブリッドマージ/挿入ソート timsort を使用します。これは、O(n)- 、既に並べ替えられたリストで-しかし、通常は対数です(論理的には、並べ替えるときの一般的なケースでは対数よりも優れていることはありません)。

2つの異なる対数ソートを考えると、特定のデータセットでは、一方が他方よりも有利になる可能性があります。従来のマージソートは変化しないため、データに関係なく同じように実行されますが、たとえば、クイックソート(対数)は変化しますが、一部のデータではパフォーマンスが向上しますが、他のデータではパフォーマンスが低下します。

3の因数(またはsortは並列化されているため3より大きい)はかなり大きいので、ディスクへのsortスワッピングなどの偶発性がないのではないかと思います。 (-Tオプションはそれが意味するように思われるでしょう)。ただし、システム時間とユーザー時間の差は、これが問題ではないことを示しています。

7
goldilocks

これは実際の回答よりも詳細な分析ですが、ソートされるデータによって異なるようです。まず、ベースの読み:

_$ 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
_

OK、python ismuchfast。ただし、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
_

それはずっと速いですが、pythonはまだ大きなマージンで勝ちます。今、再試行しますが、ソートされていません100万件のリスト:

_$ 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 sortのcmpパラメータを微調整して高速化できる場合があります)。Coreutilssortは、_-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
_

2つは同じソートされた出力を生成しないことに注意することも重要です。

_$ 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_オプションは、私のテストで大きな(または少しの)違いをもたらすようには見えませんでした。結論として、おそらくgoldilockの回答で言及されているさまざまなアルゴリズムのため、python sortはほとんどの場合高速であるように見えますが、numericalGNU sortは、並べ替えられていない数値で勝ります1


OPはおそらく 根本原因を発見 ですが、完全を期すために、最後の比較を示します。

_$ 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()を調整してテストする必要があるよりもpython-fuを使用している人を実現できます。

7
terdon