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


20

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

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

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

$ 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、およびsort8.13を使用しています


@goldilocksはい、ユーザー対リアルタイムを見てください。
8

ええ、そうです。どうやらcoreutils 8.6で並列化されたようです。
goldilocks 14年

使用可能なすべての物理メモリ--buffer-sizesort使用するように指定して、それが役立つかどうかを確認できますか?
iruvar

@ 1_CR 1 GBのバッファーで試してみましたが、タイミングに大きな変化はありません。そのうち約0.6 GBしか使用しなかったため、バッファサイズをさらに大きくしても効果はありません。
8

回答:


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-悪であるのをやめる' 14年

@Gilles locale.strxfrm並べ替えに使用するPythonスクリプトを変更すると、スクリプトは約32秒sortかかりました。
8

3
Python 2.7.3はバイト比較を行っていますが、Python3はUnicodeワード比較を行っています。Python3.3は、この「テスト」でのPython2.7の約2倍の速度です。未加工バイトをUnicode表現にパックするオーバーヘッドは、Python 2.7.3が行う必要のある既に重要なパックオブジェクトよりもさらに大きくなります。
アントン14年

2
私も同様のスローダウンを見つけましたcut。私が今持っているいくつかのマシンexport LC_ALL=C.bashrc。ただし、注意してください。これは、例に名前を付けるために、本質的に壊れていますwc(を除くwc -l)。...すべてでカウントされていない「悪いバイト」
ウォルターTross

1
このパフォーマンスの問題が発生してgrep:あなたが得ることができるかなりの特にやったときに、UTF-8無効にすることで、巨大なファイルをgrepをする際のパフォーマンスの向上を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

OK、Pythonはずっと高速です。ただし、sort数値でソートするように指示することにより、coreutilsを高速化できます。

$ 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は依然として大きなマージンで勝ちます。今、もう一度試してみましょうが、ソートされていない1M番号のリストを使用します。

$ 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

sort -nソートされていない数値データの場合、coreutils は高速です(ただし、python sortの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

また、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はほとんどの場合より高速であるように見えますが、数値 GNU 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を持っている人。


5
Pythonの並べ替えには、最後のサンプルであるASCIIの数値順序に基づいて、追加の速度の利点があります。 sort大文字/小文字比較のために少し余分な作業を行っているようです。
イズカタ14年

@Izkataそれだけです!以下の私の答えをご覧ください。
8

1
実際、Pythonには生のstdin入力から内部文字列を作成するためのかなりのオーバーヘッドがあります。数字にそれらの変換(lines = map(int, list(stdin)))と背面(stdout.writelines(map(str,lines)))全体のソートは、遅く行かせるまでの 0.234sから本当の私のマシンで0.720sに。
アントン14年

6

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

Pythonのソートは、ユニークなハイブリッドマージ/挿入ソートtimsortを使用します。これは、O(n)のベストケースシナリオとの可変数の比較を実行します。ソート時の一般的なケースでは対数より良くなることはできません)。

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

sortただし、3倍(または並列化されているため3倍以上)はかなり多く、sortディスクへのスワッピング(-Tオプションはそれを意味するように思われる)など、不測の事態がここにないのではないかと思います。ただし、システム時間とユーザー時間の関係は、これが問題ではないことを意味します。


両方の実装がCで書かれているのは良い点です。Pythonでソートアルゴリズムを実装すると、はるかに遅くなると確信しています。
8

ところで、ファイルは0から1までのランダムに生成されたフロート値で構成されているため、構造をあまり活用してはなりません。
8
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.