Dlaczego max działa wolniej niż sort?

92

Odkryłem, że maxjest wolniejszy niż sortfunkcja w Pythonie 2 i 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

Dlaczego jest max ( O(n)) wolniej niż sortfunkcja ( O(nlogn))?

WeizhongTu
źródło
3
Przeprowadziłeś analizę Python 2 raz i kod w Pythonie 3 jest dokładnie taki sam.
erip
9
a.sort()działa w miejscu. Spróbujsorted(a)
Andrea Corbellini
Jeśli to naprawiłeś, napisz, co zrobiłeś, aby to naprawić.
Precel
4
@Pretzel OP oznacza, że ​​post był edytowany, a nie, że problem został rozwiązany.
erip
2
@WeizhongTu, ale sortsortuje, a następnie ajest sortowane na zawsze
njzk2

Odpowiedzi:

125

Musisz być bardzo ostrożny używając timeitmodułu w Pythonie.

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

Tutaj kod inicjalizacyjny jest uruchamiany raz, aby utworzyć losową tablicę a. Następnie reszta kodu jest uruchamiana kilka razy. Za pierwszym razem sortuje tablicę, ale za każdym razem, gdy wywołujesz metodę sort na już posortowanej tablicy. Zwracany jest tylko najszybszy czas, więc tak naprawdę odliczasz, ile czasu zajmie Pythonowi posortowanie już posortowanej tablicy.

Częścią algorytmu sortowania Pythona jest wykrywanie, kiedy tablica jest już częściowo lub całkowicie posortowana. Po całkowitym posortowaniu musi po prostu raz przeskanować macierz, aby to wykryć, a następnie zatrzymuje się.

Jeśli zamiast tego próbowałeś:

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

wtedy sortowanie odbywa się w każdej pętli czasowej i widać, że czas na sortowanie tablicy jest rzeczywiście znacznie dłuższy niż znalezienie maksymalnej wartości.

Edit: @ skyking na odpowiedź wyjaśnia część zostawiłem niewyjaśnione: a.sort()wie, że pracuje na liście, więc można bezpośrednio uzyskać dostęp do elementów. max(a)działa na dowolnej dowolnej iteracji, więc musi używać ogólnej iteracji.

Duncan
źródło
10
Dobry chwyt. Nigdy nie zdawałem sobie sprawy, że stan interpretera jest zachowywany podczas wykonywania kodu. Teraz zastanawiam się, ile błędnych testów porównawczych wyprodukowałem w przeszłości. : -}
Frerich Raabe
1
To było dla mnie oczywiste. Ale zauważ, że nawet jeśli posortujesz już posortowaną tablicę, musisz sprawdzić wszystkie elementy. To tyle samo, co uzyskanie maksimum… Dla mnie to wygląda na połowę odpowiedzi.
Karoly Horvath
2
@KarolyHorvath, masz rację. Myślę, że @skyking dostał drugą połowę odpowiedzi: a.sort()wie, że pracuje na liście, więc ma bezpośredni dostęp do elementów. max(a)działa na dowolnej sekwencji, aby nie używać ogólnej iteracji.
Duncan,
1
@KarolyHorvath może przewidywanie gałęzi może wyjaśnić, dlaczego wielokrotne sortowanie posortowanej tablicy jest szybsze: stackoverflow.com/a/11227902/4600
marcospereira
1
@JuniorCompressor listsort.txtwyjaśnia „Ma nadprzyrodzoną wydajność na wielu rodzajach częściowo uporządkowanych tablic (potrzeba mniej niż lg (N!) Porównań i tylko N-1)”, a następnie wyjaśnia wszystkie rodzaje krwawych optymalizacji. Przypuszczam, że może dokonać wielu założeń, których maxnie może, czyli sortowanie nie jest asymptotycznie szybsze.
Frerich Raabe
86

Po pierwsze, należy zauważyć, że max()używa protokołu iteratora , podczas gdy list.sort()używa kodu ad-hoc . Oczywiście używanie iteratora jest ważnym narzutem, dlatego obserwujesz tę różnicę w czasie.

Jednak poza tym twoje testy nie są sprawiedliwe. Jesteś a.sort()na tej samej liście więcej niż raz. Algorytm Python jest zaprojektowana jako szybka już (częściowo) posortowane dane. Twoje testy wskazują, że algorytm dobrze wykonuje swoją pracę.

To są uczciwe testy:

$ 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

Tutaj za każdym razem tworzę kopię listy. Jak widać, rząd wielkości wyników jest inny: mikro- vs milisekundy, tak jak byśmy się spodziewali.

I pamiętaj: big-Oh określa górną granicę! Dolna granica algorytmu sortowania Pythona to Ω ( n ). Bycie O ( n log n ) nie oznacza automatycznie, że każdy przebieg zajmuje czas proporcjonalny do n log n . Nie oznacza to nawet, że musi być wolniejszy niż algorytm O ( n ), ale to już inna historia. Ważne jest, aby zrozumieć, że w niektórych korzystnych przypadkach algorytm O ( n log n ) może działać w czasie O ( n ) lub krótszym.

Andrea Corbellini
źródło
31

Może to być spowodowane tym, że l.sortelement członkowski listwhile maxjest funkcją ogólną. Oznacza to, że l.sortmoże polegać na wewnętrznej reprezentacji listwhile maxbędzie musiał przejść przez ogólny protokół iteratora.

To sprawia, że ​​każdy element pobiera l.sortszybciej niż każdy element, który to maxrobi.

Zakładam, że jeśli zamiast tego użyjesz sorted(a), uzyskasz wynik wolniej niż max(a).

Król nieba
źródło
5
To założenie jest tylko jedną linią czasu, aby stać się bardziej konkretnym. Nie kwestionując swojej wiedzy, tylko że taki dodatek jest trywialny dla demonstracji tych, którzy go nie znają.
Reti43
Masz rację, sorted(a)to jest wolniejsze niż max(a). Nic dziwnego, że jest to mniej więcej taka sama prędkość, jak a.sort(), ale twoje przypuszczenie, dlaczego tak nie jest - to dlatego, że OP popełnił błąd podczas testów, jak wskazano w zaakceptowanej odpowiedzi.
martineau
Chodziło o to, że istnieje możliwość, że ogólny protokół iteratora ma wystarczający narzut, aby zrównoważyć log(n)czynnik złożoności. Oznacza to, że O(n)algorytm ma gwarancję, że będzie szybszy niż O(nlogn)algorytm dla wystarczająco dużych n(na przykład dlatego, że czas każdej operacji może się różnić między algorytmami - nlognszybkie kroki mogą być szybsze niż nwolne). Dokładnie tam, gdzie próg rentowności nie jest brany pod uwagę w tym przypadku (ale należy mieć świadomość, że log nwspółczynnik nie jest bardzo dużym czynnikiem dla małych n).
jazda na nartach