Pourquoi max est-il plus lent que le tri?

92

J'ai trouvé que maxc'est plus lent que la sortfonction en Python 2 et 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

Pourquoi est max ( O(n)) plus lente que la sortfonction ( O(nlogn))?

WeizhongTu
la source
3
Vous avez exécuté l'analyse Python 2 une fois et le code Python 3 est exactement le même.
erip le
9
a.sort()fonctionne en place. Essayezsorted(a)
Andrea Corbellini
Si vous l'avez corrigé, renvoyez ce que vous avez fait pour le réparer, s'il vous plaît.
Pretzel le
4
@Pretzel OP signifie que le message a été modifié, pas que le problème a été résolu.
erip le
2
@WeizhongTu mais sorttrie, puis aest trié pour toujours
njzk2

Réponses:

125

Vous devez être très prudent lorsque vous utilisez le timeitmodule en Python.

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

Ici, le code d'initialisation s'exécute une fois pour produire un tableau aléatoire a. Ensuite, le reste du code est exécuté plusieurs fois. La première fois, il trie le tableau, mais chaque fois que vous appelez la méthode de tri sur un tableau déjà trié. Seul le temps le plus rapide est renvoyé, vous chronométrez donc en fait le temps qu'il faut à Python pour trier un tableau déjà trié.

Une partie de l'algorithme de tri de Python consiste à détecter lorsque le tableau est déjà partiellement ou complètement trié. Une fois complètement trié, il doit simplement balayer une fois la matrice pour le détecter, puis il s'arrête.

Si à la place vous avez essayé:

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

puis le tri se produit sur chaque boucle de synchronisation et vous pouvez voir que le temps de tri d'un tableau est en effet beaucoup plus long que de simplement trouver la valeur maximale.

Edit: la réponse de @ skyking explique la partie que j'ai laissée inexpliquée: a.sort()sait qu'elle travaille sur une liste et peut donc accéder directement aux éléments. max(a)fonctionne sur toute itération arbitraire doit donc utiliser une itération générique.

Duncan
la source
10
Bonne prise. Je n'ai jamais réalisé que l'état de l'interpréteur est conservé pendant les exécutions de code. Maintenant, je me demande combien de benchmarks défectueux j'ai produits dans le passé. : -}
Frerich Raabe
1
C'était évident pour moi. Mais notez que même si vous triez un tableau déjà trié, vous devez vérifier tous les éléments. Ce qui est tout autant de travail que d'obtenir le maximum ... Pour moi, cela ressemble à une demi-réponse.
Karoly Horvath le
2
@KarolyHorvath, vous avez raison. Je pense que @skyking a obtenu l'autre moitié de la réponse: a.sort()sait qu'il travaille sur une liste et peut donc accéder directement aux éléments. max(a)fonctionne sur une séquence arbitraire pour ne pas utiliser une itération générique.
Duncan
1
@KarolyHorvath peut-être que la prédiction de branche peut expliquer pourquoi le tri répété d'un tableau trié est plus rapide: stackoverflow.com/a/11227902/4600
marcospereira
1
@JuniorCompressor listsort.txtexplique "Il a des performances surnaturelles sur de nombreux types de tableaux partiellement ordonnés (moins de comparaisons lg (N!) Nécessaires, et aussi peu que N-1)" et continue ensuite à expliquer toutes sortes d'optimisations sanglantes. Je suppose qu'il peut faire beaucoup d'hypothèses qui maxne le peuvent pas, c'est-à-dire que le tri n'est pas asymptotiquement plus rapide.
Frerich Raabe
87

Tout d'abord, notez qu'il max()utilise le protocole itérateur , tout en list.sort()utilisant un code ad hoc . De toute évidence, l'utilisation d'un itérateur est une surcharge importante, c'est pourquoi vous observez cette différence de temps.

Cependant, à part cela, vos tests ne sont pas équitables. Vous exécutez a.sort()plusieurs fois sur la même liste. L' algorithme utilisé par Python est spécifiquement conçu pour être rapide pour les données déjà (partiellement) triées. Vos tests indiquent que l'algorithme fait bien son travail.

Ce sont des tests équitables:

$ 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

Ici, je crée une copie de la liste à chaque fois. Comme vous pouvez le voir, l'ordre de grandeur des résultats est différent: micro- vs millisecondes, comme on pouvait s'y attendre.

Et rappelez-vous: big-Oh spécifie une limite supérieure! La limite inférieure de l'algorithme de tri de Python est Ω ( n ). Être O ( n log n ) n'implique pas automatiquement que chaque exécution prend un temps proportionnel à n log n . Cela n'implique même pas qu'il doit être plus lent qu'un algorithme O ( n ), mais c'est une autre histoire. Ce qu'il est important de comprendre, c'est que dans certains cas favorables, un algorithme O ( n log n ) peut s'exécuter en un temps O ( n ) ou moins.

Andrea Corbellini
la source
31

Cela peut être dû au l.sortfait que est membre de listwhile maxest une fonction générique. Cela signifie que l.sortpeut s'appuyer sur la représentation interne de listwhilemax devra passer par le protocole générique d'itérateur.

Cela fait que chaque élément recherché l.sortest plus rapide que chaque élément extrait quemax fait.

Je suppose que si vous utilisez à la place, sorted(a)vous obtiendrez le résultat plus lentement que max(a).

skier
la source
5
Cette hypothèse n'est qu'à un moment donné pour devenir plus concret. Ne pas remettre en question vos connaissances, juste qu'un tel ajout est trivial pour la démonstration de ceux qui ne le savent pas.
Reti43 le
Vous avez raison, c'est sorted(a)plus lent que max(a). Sans surprise, c'est à peu près la même vitesse que a.sort(), mais votre conjecture quant à la raison ne l'est pas - c'est parce que l'OP a commis une erreur dans ses tests, comme indiqué dans la réponse acceptée.
martineau
Le fait est qu'il est possible que le protocole d'itérateur générique ait une surcharge suffisante pour compenser le log(n)facteur de complexité. C'est-à-dire qu'un O(n)algorithme n'est garanti que plus rapide qu'un O(nlogn)algorithme pour suffisamment grand n(par exemple parce que le temps de chaque opération peut différer entre les algorithmes - nlognles étapes rapides peuvent être plus rapides que nles étapes lentes). L'endroit exact où le seuil de rentabilité n'est pas pris en compte dans ce cas (mais il faut être conscient que le log nfacteur n'est pas un facteur très important pour les petits n).
skyking