Aller au contenu

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 :

python -m cProfile -s cumtime mon_script.py

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é.

__slots__ pour réduire la mémoire avancé#

class Point:
    __slots__ = ("x", "y")   # pas de __dict__ par instance : moins de mémoire, accès plus rapide

    def __init__(self, x, y):
        self.x = x
        self.y = y