| name | python-performances |
| description | Optimisation de performances Python — profiling, caching, vectorisation, compilation (Cython/Numba), multiprocessing, et bonnes pratiques. En français. |
Performances Python — Guide Complet (Français)
Ce skill couvre les techniques d'optimisation et de profiling en Python. À charger pour toute tâche de performance.
1. Profiling : Trouver les Goulots
cProfile (profilage déterministe)
python -m cProfile -s cumulative mon_script.py
python -m cProfile -s tottime mon_script.py
import cProfile
import pstats
from pstats import SortKey
def fonction_a_profiler():
...
profiler = cProfile.Profile()
profiler.enable()
fonction_a_profiler()
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats(SortKey.CUMULATIVE)
stats.print_stats(20)
timeit (micro-benchmarks)
import timeit
temps_liste = timeit.timeit(
"[x**2 for x in range(1000)]",
number=10000,
)
temps_map = timeit.timeit(
"list(map(lambda x: x**2, range(1000)))",
number=10000,
)
print(f"Compréhension : {temps_liste:.4f}s")
print(f"map() : {temps_map:.4f}s")
python -m timeit -s "xs = list(range(1000))" "[x**2 for x in xs]"
Scalene (profiling CPU + mémoire)
pip install scalene
scalene --html --outfile profil.html mon_script.py
py-spy (profiling de processus en cours)
pip install py-spy
py-spy top --pid 12345
py-spy record -o profil.svg --pid 12345
2. Structures de Données Performantes
from collections import deque
file = deque()
file.append(1)
file.appendleft(0)
file.pop()
from array import array
entiers = array("i", [1, 2, 3])
import heapq
tas = [3, 1, 4, 1, 5]
heapq.heapify(tas)
heapq.heappush(tas, 0)
resultat = []
for x in range(1000):
resultat.append(x ** 2)
resultat = [x ** 2 for x in range(1000)]
3. Optimisation de Chaînes
resultat = ""
for mot in mots:
resultat += mot + " "
resultat = " ".join(mots)
nom, age = "Alice", 30
message = f"{nom} a {age} ans"
if nom_de_fichier.endswith((".py", ".pyw")):
...
table = str.maketrans("éèêë", "eeee")
resultat = texte.translate(table)
4. NumPy : Vectorisation
import numpy as np
donnees = list(range(1_000_000))
carres = [x ** 2 for x in donnees]
arr = np.arange(1_000_000)
carres = arr ** 2
Conversion des boucles en opérations vectorisées
resultat = []
for i in range(len(a)):
if a[i] > 0:
resultat.append(a[i] * 2)
mask = a > 0
resultat = a[mask] * 2
resultat = np.where(a > 0, a * 2, a)
conditions = [a < 0, (a >= 0) & (a < 10), a >= 10]
choix = [0, a * 2, a ** 2]
resultat = np.select(conditions, choix)
5. Mise en Cache
lru_cache (fonctions pures)
from functools import lru_cache, cache
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
"""Calcule le n-ième terme de Fibonacci avec cache.
Args:
n: Position dans la suite.
Returns:
Le terme correspondant.
"""
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
@cache
def factorielle(n: int) -> int:
if n <= 1:
return 1
return n * factorielle(n - 1)
print(fibonacci.cache_info())
fibonacci.cache_clear()
Dict de cache manuel
from typing import Any
_cache: dict[tuple, Any] = {}
def calcul_couteux(x: int, y: int) -> float:
cle = (x, y)
if cle not in _cache:
_cache[cle] = x ** y + y ** x
return _cache[cle]
6. Itérations Efficaces
somme = sum([x**2 for x in range(1_000_000)])
somme = sum(x**2 for x in range(1_000_000))
if any(x < 0 for x in grande_liste):
...
for i, valeur in enumerate(sequence):
...
for a, b in zip(liste_a, liste_b):
...
for cle, valeur in dico.items():
...
7. Cython et Numba
Numba (compilation JIT)
from numba import jit, njit, prange
import numpy as np
@njit
def somme_numba(arr: np.ndarray) -> float:
"""Somme d'un tableau compilée avec Numba.
Args:
arr: Tableau NumPy 1D.
Returns:
Somme des éléments.
"""
total = 0.0
for x in arr:
total += x
return total
@njit(parallel=True)
def somme_parallele(arr: np.ndarray) -> float:
total = 0.0
for i in prange(len(arr)):
total += arr[i]
return total
Cython (.pyx)
# mon_module.pyx
# Compilation : cythonize -i mon_module.pyx
def fibonacci_cython(int n):
"""Version Cython de Fibonacci."""
cdef int a = 0, b = 1, i
for i in range(n):
a, b = b, a + b
return a
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("mon_module.pyx"))
8. Multiprocessing
from multiprocessing import Pool, cpu_count
from concurrent.futures import ProcessPoolExecutor
def calcul_intensif(param: int) -> float:
"""Fonction intensive CPU à paralléliser."""
return sum(i ** 2 for i in range(param))
def traiter_en_parallele(
parametres: list[int],
max_workers: int | None = None,
) -> list[float]:
"""Traite une liste de paramètres en parallèle.
Args:
parametres: Liste des paramètres d'entrée.
max_workers: Nombre de processus (par défaut : CPU count).
Returns:
Résultats dans l'ordre.
"""
with ProcessPoolExecutor(max_workers=max_workers) as executor:
resultats = list(executor.map(calcul_intensif, parametres))
return resultats
8. Lazy Evaluation
def lire_grand_fichier(chemin: str) -> Generator[str, None, None]:
"""Lit un fichier ligne par ligne sans tout charger.
Args:
chemin: Chemin du fichier.
Yields:
Chaque ligne (sans le saut de ligne).
"""
with open(chemin) as f:
for ligne in f:
yield ligne.rstrip("\n")
from itertools import islice, chain, groupby
premiers_10 = list(islice(generateur_infini(), 10))
tous = chain(liste_a, liste_b, generateur_c())
9. Entrées/Sorties Optimisées
import json
import pickle
from pathlib import Path
contenu = Path("fichier.json").read_text()
donnees = json.loads(contenu)
with open("fichier.json") as f:
donnees = json.load(f)
with open("cache.pkl", "wb") as f:
pickle.dump(donnees, f, protocol=pickle.HIGHEST_PROTOCOL)
import mmap
with open("enorme_fichier.bin", "r+b") as f:
with mmap.mmap(f.fileno(), 0) as mm:
print(mm[1000:1100])
10. Règles d'Or de l'Optimisation
- Profiler avant d'optimiser — ne devinez pas, mesurez
- La complexité algorithmique prime — O(n) > micro-optimisations
- Vectoriser avec NumPy quand on manipule des tableaux
- Cache pour les fonctions pures —
@lru_cache
- Générateurs > listes pour les grandes séquences
- join() > += pour les chaînes
- locals > globals — accès plus rapide
.copy() > copie manuelle — implémenté en C
- sets/dicts > listes pour les tests d'appartenance (O(1) vs O(n))
- Multiprocessing pour le CPU-bound, asyncio pour l'I/O-bound
Références