Performance#
avancé
Choisir la bonne structure de données#
| Besoin | Structure | Complexité |
|---|---|---|
Recherche d'appartenance (in) |
set / dict |
O(1) en moyenne |
| Recherche d'appartenance | list |
O(n) |
| Ajout/retrait en fin de liste | list.append/pop |
O(1) amorti |
| Ajout/retrait en début | collections.deque |
O(1) |
Ajout/retrait en début d'une list |
list.insert(0, x) |
O(n), à éviter |
# Lent sur une grande liste
if valeur in grande_liste: # O(n)
...
# Rapide
grande_liste_set = set(grande_liste)
if valeur in grande_liste_set: # O(1)
...
Concaténation de chaînes#
# À éviter en boucle : O(n²), chaque + crée une nouvelle chaîne
resultat = ""
for mot in mots:
resultat += mot
# Préférable : O(n)
resultat = "".join(mots)
Générateurs plutôt que listes intermédiaires#
# Construit toute la liste en mémoire avant de sommer
total = sum([x**2 for x in range(10_000_000)])
# Ne garde qu'un élément à la fois en mémoire
total = sum(x**2 for x in range(10_000_000))
Éviter les accès répétés coûteux#
# Ré-évalue len(l) et accède à l'attribut à chaque itération
for i in range(len(objet.liste)):
traiter(objet.liste[i])
# Une seule résolution
liste = objet.liste
for item in liste:
traiter(item)
Mesurer avant d'optimiser#
import time
debut = time.perf_counter() # horloge monotone haute résolution
resultat = calcul(donnees)
print(f"{time.perf_counter() - debut:.4f} s")
Pour comparer deux implémentations courtes, timeit répète la mesure et lisse le bruit :
import timeit
timeit.timeit("'-'.join(str(x) for x in range(100))", number=10000)
# En ligne de commande
# python -m timeit "'-'.join(str(x) for x in range(100))"
Pour localiser le goulot d'étranglement d'un programme entier :
Piège
Ne pas utiliser time.time() pour chronométrer : elle suit l'horloge système et peut reculer. Utiliser time.perf_counter().
Ne pas micro-optimiser sans mesure : la lisibilité prime tant que la performance n'est pas un problème constaté.