Tại sao coreutils sắp xếp chậm hơn Python?


20

Tôi đã viết đoạn script sau để kiểm tra tốc độ của chức năng sắp xếp của Python:

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

Sau đó tôi đã so sánh điều này với sortlệnh coreutils trên một tệp chứa 10 triệu dòng:

$ 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

Lệnh tích hợp đã sử dụng cả bốn CPU (Python chỉ sử dụng một) nhưng mất khoảng 3 lần thời gian để chạy! Đưa cái gì?

Tôi đang sử dụng Ubuntu 12.04.5 (32-bit), Python 2.7.3 và sort8.13


@goldilocks Đúng vậy, hãy nhìn người dùng so với thời gian thực.
tám

Huh - bạn nói đúng. Rõ ràng nó đã được song song trong coreutils 8.6.
goldilocks

Bạn có thể sử dụng --buffer-sizeđể chỉ định rằng sortsử dụng tất cả bộ nhớ vật lý có sẵn và xem nếu điều đó giúp?
iruvar

@ 1_CR Đã thử nó với bộ đệm 1 GB, không có thay đổi đáng kể về thời gian. Nó chỉ được sử dụng khoảng 0,06 GB, vì vậy việc tăng kích thước bộ đệm sẽ không giúp ích gì thêm.
augurar

Câu trả lời:


22

Nhận xét của Izkata đã tiết lộ câu trả lời: so sánh cụ thể theo địa phương. Các sortlệnh sử dụng ngôn ngữ được chỉ ra bởi môi trường, trong khi giá trị mặc định Python để so sánh thứ tự byte. So sánh các chuỗi UTF-8 khó hơn so với các chuỗi byte.

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

Thế còn cái đó.


Và làm thế nào để họ so sánh cho chuỗi UTF-8?
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles Sửa đổi tập lệnh Python để sử dụng locale.strxfrmđể sắp xếp, tập lệnh mất ~ 32 giây, vẫn nhanh hơn sortnhưng ít hơn nhiều.
augurar

3
Python 2.7.3 đang thực hiện so sánh byte, nhưng Python3 sẽ thực hiện so sánh từ unicode. Python3.3 chậm gấp đôi Python2.7 cho "bài kiểm tra" này. Chi phí hoạt động của việc đóng gói các byte thô thành biểu diễn Unicode thậm chí còn cao hơn các đối tượng đóng gói quan trọng mà Python 2.7.3 phải thực hiện.
Anthon

2
Tôi cũng thấy chậm lại cutvà những người khác cũng vậy. Trên một số máy bây giờ tôi có export LC_ALL=Ctrong .bashrc. Nhưng hãy cẩn thận: điều này về cơ bản phá vỡ wc(ngoại trừ wc -l), chỉ để đặt tên cho một ví dụ. "Xấu byte" không được tính ở tất cả ...
Walter Tross

1
Vấn đề về hiệu suất này cũng xảy ra với grep: bạn có thể cải thiện hiệu suất đáng kể khi lấy các tệp lớn bằng cách vô hiệu hóa UTF-8, đặc biệt là khi thực hiệngrep -i
Adrian Pronk

7

Đây là một phân tích bổ sung hơn là một câu trả lời thực tế nhưng nó dường như thay đổi tùy thuộc vào dữ liệu được sắp xếp. Đầu tiên, một bài đọc cơ bản:

$ 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 nhanh hơn nhiều . Tuy nhiên, bạn có thể làm cho coreutils sortnhanh hơn bằng cách yêu cầu nó sắp xếp theo số lượng:

$ 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

Điều đó nhanh hơn nhiều nhưng trăn vẫn chiến thắng với tỷ suất lợi nhuận cao. Bây giờ, hãy thử lại nhưng với danh sách các số 1M không được sắp xếp:

$ 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 -nnhanh hơn cho dữ liệu số chưa được sắp xếp (mặc dù bạn có thể điều chỉnh cmptham số sắp xếp của python để làm cho nó nhanh hơn). Coreutils sortvẫn chậm hơn đáng kể khi không có -ncờ. Vì vậy, những gì về các ký tự ngẫu nhiên, không phải là số thuần túy?

$ 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 vẫn đánh bại coreutils nhưng với biên độ nhỏ hơn nhiều so với những gì bạn thể hiện trong câu hỏi của mình. Đáng ngạc nhiên, nó vẫn nhanh hơn khi nhìn vào dữ liệu bảng chữ cái thuần túy:

$ 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

Cũng cần lưu ý rằng cả hai không tạo ra cùng một đầu ra được sắp xếp:

$ 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

Thật kỳ lạ, --buffer-sizetùy chọn dường như không tạo ra nhiều sự khác biệt (hoặc bất kỳ) trong các thử nghiệm của tôi. Tóm lại, có lẽ là do các thuật toán khác nhau được đề cập trong câu trả lời của goldilock, python sortdường như nhanh hơn trong hầu hết các trường hợp nhưng GNU số đãsort đánh bại nó trên các số chưa được sắp xếp 1 .


OP có thể đã tìm ra nguyên nhân gốc rễ nhưng vì mục đích hoàn chỉnh, đây là so sánh cuối cùng:

$ 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 Ai đó có nhiều python-fu hơn tôi nên thử kiểm tra tinh chỉnh list.sort()để xem tốc độ tương tự có thể đạt được bằng cách chỉ định phương pháp sắp xếp.


5
Python sort có một lợi thế tốc độ bổ sung, dựa trên mẫu cuối cùng của bạn: thứ tự số ASCII. sortdường như đang làm một chút công việc phụ để so sánh chữ hoa / chữ thường.
Izkata

@Izkata Đó là nó! Xem câu trả lời của tôi dưới đây.
augurar

1
Trên thực tế, python có khá nhiều chi phí tạo ra các chuỗi bên trong của nó ra khỏi stdinđầu vào thô . Việc chuyển đổi chúng thành số ( lines = map(int, list(stdin))) và back ( stdout.writelines(map(str,lines))) làm cho toàn bộ quá trình sắp xếp chậm hơn, tăng từ 0,234 giây thành 0,720 trên máy của tôi.
Anthon

6

Cả hai triển khai đều ở C, vì vậy một sân chơi bình đẳng ở đó. Coreutils sort rõ ràng sử dụng thuật toán sáp nhập . Mergesort thực hiện một số phép so sánh cố định làm tăng logarit theo kích thước đầu vào, tức là O lớn (n log n).

Sắp xếp của Python sử dụng một sắp xếp hợp nhất / chèn hỗn hợp duy nhất, timsort , sẽ thực hiện một số lượng so sánh khác nhau với kịch bản trường hợp tốt nhất là O (n) - có lẽ, trong danh sách đã được sắp xếp - nhưng nói chung là logarit (về mặt logic, bạn không thể tốt hơn logarit cho trường hợp chung khi sắp xếp).

Đưa ra hai loại logarit khác nhau, người ta có thể có lợi thế hơn người kia trên một số tập dữ liệu cụ thể. Một loại hợp nhất truyền thống không thay đổi, do đó, nó sẽ thực hiện giống nhau bất kể dữ liệu, nhưng ví dụ, quicksort (cũng logarit), không thay đổi, sẽ hoạt động tốt hơn trên một số dữ liệu nhưng kém hơn trên các dữ liệu khác.

sortMặc dù vậy, một yếu tố ba (hoặc nhiều hơn 3, song song) là khá ít, điều này khiến tôi tự hỏi liệu không có một số dự phòng ở đây, chẳng hạn như sorttrao đổi vào đĩa ( -Ttùy chọn dường như ngụ ý nó). Tuy nhiên, hệ thống thấp của bạn so với thời gian người dùng ngụ ý đây không phải là vấn đề.


Điểm hay là cả hai triển khai đều được viết bằng C. Tôi chắc chắn nếu tôi triển khai một thuật toán sắp xếp trong Python thì nó sẽ chậm hơn rất nhiều.
augurar

Nhân tiện, tệp bao gồm các giá trị float được tạo ngẫu nhiên trong khoảng từ 0 đến 1, do đó không nên có quá nhiều cấu trúc để khai thác.
augurar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.