Mengapa max lebih lambat dari sort?

92

Saya telah menemukan itu maxlebih lambat daripada sortfungsi di Python 2 dan 3.

Python 2

$ python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'a.sort();a[-1]'
1000 loops, best of 3: 239 usec per loop
$ python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'max(a)'        
1000 loops, best of 3: 342 usec per loop

Python 3

$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'a.sort();a[-1]'
1000 loops, best of 3: 252 usec per loop
$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'max(a)'
1000 loops, best of 3: 371 usec per loop

Mengapa adalah max ( O(n)) lebih lambat dibandingkan dengan sortfungsi ( O(nlogn))?

WeizhongTu
sumber
3
Anda menjalankan analisis Python 2 sekali dan kode Python 3 sama persis.
erip
9
a.sort()bekerja di tempat. Cobasorted(a)
Andrea Corbellini
Jika Anda memperbaikinya, kirimkan kembali apa yang telah Anda lakukan untuk memperbaikinya.
Pretzel
4
@Pretzel OP berarti kiriman tersebut telah diedit, bukan berarti masalahnya telah diperbaiki.
erip
2
@WeizhongTu tapi sortsemacam, dan kemudian adiurutkan selamanya
njzk2

Jawaban:

125

Anda harus sangat berhati-hati saat menggunakan timeitmodul dengan Python.

python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'a.sort();a[-1]'

Di sini kode inisialisasi dijalankan sekali untuk menghasilkan larik acak a. Kemudian sisa kode dijalankan beberapa kali. Pertama kali ini mengurutkan larik, tetapi setiap kali Anda memanggil metode pengurutan pada larik yang sudah diurutkan. Hanya waktu tercepat yang dikembalikan, jadi Anda sebenarnya menghitung waktu berapa lama waktu yang dibutuhkan Python untuk mengurutkan array yang sudah diurutkan.

Bagian dari algoritma pengurutan Python adalah untuk mendeteksi ketika larik sudah sebagian atau seluruhnya diurutkan. Ketika benar-benar diurutkan, ia hanya perlu memindai sekali melalui array untuk mendeteksi ini dan kemudian berhenti.

Jika sebaliknya Anda mencoba:

python -m timeit -s 'import random;a=range(100000);random.shuffle(a)' 'sorted(a)[-1]'

kemudian pengurutan terjadi pada setiap putaran waktu dan Anda dapat melihat bahwa waktu untuk mengurutkan array memang jauh lebih lama daripada hanya menemukan nilai maksimum.

Edit: @ Skyking ini jawabannya menjelaskan bagian saya meninggalkan dijelaskan: a.sort()tahu itu bekerja pada daftar sehingga dapat langsung mengakses elemen. max(a)bekerja pada sembarang iterable sehingga harus menggunakan iterasi generik.

Duncan
sumber
10
Tangkapan yang bagus. Saya tidak pernah menyadari bahwa status interpreter dipertahankan di seluruh kode yang dijalankan. Sekarang saya bertanya-tanya berapa banyak tolok ukur yang salah yang saya buat di masa lalu. : -}
Frerich Raabe
1
Itu sangat jelas bagi saya. Tetapi perhatikan bahwa meskipun Anda mengurutkan array yang sudah diurutkan, Anda harus memeriksa semua elemen. Yang sama banyaknya dengan mendapatkan hasil maksimal .... Bagi saya ini tampak seperti jawaban setengah.
Karoly Horvath
2
@Karolyorv, Anda benar. Saya pikir @skyking mendapatkan separuh jawaban lainnya: a.sort()tahu itu berfungsi pada daftar sehingga dapat langsung mengakses elemen. max(a)bekerja pada urutan arbitrer untuk tidak menggunakan iterasi generik.
Duncan
1
@KarolyHorvath mungkin prediksi cabang dapat menjelaskan mengapa berulang kali mengurutkan array yang diurutkan lebih cepat: stackoverflow.com/a/11227902/4600
marcospereira
1
@JuniorCompressor listsort.txtmenjelaskan "Ini memiliki kinerja supernatural pada banyak jenis larik yang tersusun sebagian (kurang dari lg (N!) Diperlukan perbandingan, dan sesedikit N-1)" dan kemudian menjelaskan semua jenis pengoptimalan berdarah. Saya kira ini bisa membuat banyak asumsi yang maxtidak bisa, yaitu pengurutan tidak lebih cepat secara asimtotik.
Frerich Raabe
87

Pertama, perhatikan yang max()menggunakan protokol iterator , sementara list.sort()menggunakan kode ad-hoc . Jelas, menggunakan iterator adalah overhead yang penting, itulah mengapa Anda mengamati perbedaan waktu itu.

Namun, selain itu, pengujian Anda tidak adil. Anda menjalankan a.sort()daftar yang sama lebih dari sekali. The algoritma yang digunakan oleh Python secara khusus dirancang untuk menjadi cepat untuk sudah (sebagian) diurutkan data. Pengujian Anda menunjukkan bahwa algoritme tersebut melakukan tugasnya dengan baik.

Ini adalah tes yang adil:

$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'max(a[:])'
1000 loops, best of 3: 227 usec per loop
$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'a[:].sort()'
100 loops, best of 3: 2.28 msec per loop

Di sini saya membuat salinan daftar setiap saat. Seperti yang Anda lihat, urutan besarnya hasil berbeda: mikro vs milidetik, seperti yang kita perkirakan.

Dan ingat: big-Oh menentukan batas atas! Batas bawah untuk algoritma pengurutan Python adalah Ω ( n ). Menjadi O ( n log n ) tidak secara otomatis menyiratkan bahwa setiap proses membutuhkan waktu yang sebanding dengan n log n . Ini bahkan tidak menyiratkan bahwa itu harus lebih lambat dari algoritma O ( n ), tapi itu cerita lain. Yang penting untuk dipahami adalah bahwa dalam beberapa kasus yang menguntungkan, algoritme O ( n log n ) dapat berjalan dalam waktu O ( n ) atau kurang.

Andrea Corbellini
sumber
31

Ini bisa jadi karena l.sortadalah anggota listwhile maxadalah fungsi generik. Ini berarti bahwa l.sortdapat mengandalkan representasi internal listsementara maxharus melalui protokol iterator generik.

Ini membuat setiap pengambilan elemen l.sortlebih cepat daripada setiap elemen yang diambil max.

Saya berasumsi bahwa jika Anda malah menggunakan sorted(a)Anda akan mendapatkan hasil lebih lambat dari max(a).

skyking
sumber
5
Asumsi itu hanya selangkah lagi untuk menjadi lebih konkret. Bukan mempersoalkan pengetahuan Anda, hanya saja penambahan semacam itu sepele untuk demonstrasi mereka yang belum mengetahuinya.
Reti43
Anda benar, itu sorted(a)lebih lambat dari max(a). Tidak mengherankan Kecepatannya hampir sama dengan a.sort(), tetapi dugaan Anda tentang alasan mengapa tidak — itu karena OP membuat kesalahan dalam pengujian mereka seperti yang ditunjukkan dalam jawaban yang diterima.
martineau
Intinya adalah ada kemungkinan bahwa protokol iterator generik memiliki overhead yang cukup untuk mengimbangi log(n)faktor kerumitan. Artinya suatu O(n)algoritma hanya dijamin akan lebih cepat daripada O(nlogn)algoritma yang cukup besar n(misalnya karena waktu untuk setiap operasi mungkin berbeda antar algoritma - nlognlangkah cepat mungkin lebih cepat daripada nlangkah lambat). Persis di mana titik impas tidak dipertimbangkan dalam kasus ini (tetapi orang harus menyadari bahwa log nfaktor tersebut bukanlah faktor yang sangat besar untuk bertubuh kecil n).
skyking