Reading Time: 10 minutes

Points à retenir clés

  • profil avant d’optimiser. Ne devinez pas quelle partie de votre solveur PDE est lente. Mesurez-le d’abord. Le véritable goulot d’étranglement n’est souvent pas celui que vous attendez.
  • Les profileurs Python sont pratiques. Des outils tels que cProfile, py-spy, scalene et SnakeViz prennent chacun en charge des besoins de profilage différents.
  • Les goulots d’étranglement courants du solveur PDE comprennent l’assemblage de la matrice, les itérations de solveur linéaire, la surcharge de niveau Python dans la construction d’équations et les modèles d’allocation de mémoire.
  • Le flux de travail Measure-Diagnose-Treat transforme de vagues problèmes de performances en résultats concrets et réparables.
  • La mise à l’échelle change ce qui est lent. Un solveur qui se sent rapide sur une petite grille peut exposer différents goulots d’étranglement à des tailles de problème réalistes.

Votre solveur est lent. Et maintenant ?

Vous configurez une simulation. Vous l’exécutez. Ensuite, cela prend beaucoup trop de temps.

L’instinct naturel est de commencer à changer de code. Vous voudrez peut-être vectoriser des boucles, échanger des solveurs ou ajouter du parallélisme. Mais il y a un problème que de nombreux chercheurs sautent : vous ne savez peut-être pas ce qui est réellement lent.

La partie qui ressemble au goulot d’étranglement peut ne prendre qu’une petite partie du temps d’exécution. Pendant ce temps, une étape d’assemblage de matrice cachée peut consommer la majeure partie du temps d’exécution.

Le profilage des performances n’est pas facultatif. C’est l’une des étapes les plus importantes pour rendre un solveur PDE plus rapide, et il est plus facile que ne le pensent de nombreux chercheurs en Python.

Ce guide explique les outils, le flux de travail et les goulots d’étranglement courants que vous pouvez rencontrer lors du profilage du code Python scientifique.

Qu’est-ce qu’un profileur et pourquoi est-ce important ?

Un profileur est un outil qui enregistre les fonctions que votre programme appelle, la durée de chaque fonction et la fréquence à laquelle chaque fonction est invoquée. Il produit un rapport, souvent en texte brut ou sous forme de graphique visuel interactif, qui indique où votre code passe du temps.

Sans profileur, vous optimisez en fonction de l’intuition. Dans le travail de performance, l’intuition est souvent erronée. Vous pouvez passer des heures à améliorer une fonction qui ne prend qu’une petite partie du temps d’exécution tout en ignorant la fonction qui domine l’ensemble de l’exécution.

Avec un profileur, vous voyez les données. Vous arrêtez de deviner et commencez à cibler de vrais problèmes de performances.

Le flux de travail de profilage : mesurer, diagnostiquer, traiter

Avant de choisir un outil, il est utile de comprendre le flux de travail. Un processus de profilage pratique comporte trois étapes :

  1. mesurer. Utilisez un profileur pour trouver quelle partie du code est réellement lente.
  2. Diagnostiquer. Comprenez pourquoi il est lent. La cause peut être l’allocation de mémoire, le choix de l’algorithme, la surcharge de niveau Python ou la configuration du solveur.
  3. traiter. Corrigez la cause première au lieu d’appliquer des optimisations aléatoires.

Ceci est similaire à un diagnostic médical. Un médecin ne commence pas le traitement avant de vérifier les symptômes et les résultats des tests. Le profilage fonctionne de la même manière. Vous mesurez avant de modifier le code.

Un détail clé est la taille du problème. Votre exécution de profilage doit utiliser une taille de problème qui représente votre charge de travail réelle. Si vous ne profilez qu’avec une petite grille, vous risquez de manquer les goulots d’étranglement qui n’apparaissent qu’à l’échelle.

Outils de profilage Python : la boîte à outils pratique

Python a plusieurs options de profilage utiles. Pour le calcul scientifique, les outils suivants sont particulièrement pratiques.

Cprofile : intégré, zéro installation, toujours disponible

cProfile est livré avec chaque installation Python. Il s’agit d’une extension C qui se situe entre l’interpréteur et votre code, comptant les opérations avec des frais généraux relativement faibles.

import cProfile

# Profile a single call
cProfile.run('equation.solve(var=phi, dt=timeStep)', 'fi_py_profile.prof')

Vous pouvez également l’exécuter à partir de la ligne de commande :

$ python -m cProfile -s time my_solver.py 0 1.0 0.1 1000000 output.dat | head -n 20

cProfile est le meilleur pour un profilage rapide en premier. Vous n’avez pas besoin d’installer quoi que ce soit et cela peut attraper des goulots d’étranglement évidents.

Les options de tri sont importantes. -s time Affiche l’heure interne. -s cumulative Affiche le temps total, y compris les sous-appels. -s ncalls Affiche le nombre d’appels. Choisissez l’option qui répond à votre question.

Py-Spy : profileur d’échantillonnage en direct et à faibles frais généraux

py-spy est un profileur d’échantillonnage écrit en rouille. Il peut surveiller un processus Python en cours d’exécution sans le redémarrer. Vous l’attachez, enregistrez le processus et obtenez une sortie visuelle.

$ pip install py-spy
$ py-spy dump -- python my_solver.py
$ py-spy record -o profile.svg -- python my_solver.py

py-spy est utile pour le code de production, les simulations à long terme où le redémarrage est peu pratique et la détection en temps réel des goulots d’étranglement.

Les scientifiques l’aiment souvent parce qu’il ne nécessite pas de modifications de code, ne redémarre pas le programme et a peu de frais généraux.

Scalene : CPU, GPU et mémoire dans un seul outil

scalene est un profileur haute performance qui mesure l’utilisation du processeur, l’utilisation du GPU et l’allocation de mémoire. Il produit des pannes ligne par ligne et peut aider à identifier les fuites de mémoire.

$ pip install scalene
$ scalene my_solver.py

scalene est utile pour les charges de travail accélérées par GPU, les simulations lourdes en mémoire et les cas où vous avez besoin d’une analyse CPU et de mémoire dans un seul outil.

Cela compte parce que les goulots d’étranglement en python scientifique ne sont pas toujours de purs problèmes de CPU. Une opération de tableau dense peut passer plus de temps à allouer des tableaux temporaires qu’à effectuer des calculs.

Snakeviz : résultats visuels

cProfile La sortie est basée sur le texte. SnakeViz convertit cette sortie en un graphique visuel interactif. Vous pouvez inspecter les appels de fonctions imbriqués, comparer le temps passé dans différentes branches et identifier rapidement les puits de temps profonds.

$ pip install snakeviz
$ snakeviz fi_py_profile.prof

SnakeViz est utile lorsque vous devez présenter des résultats de profilage à des collaborateurs, comprendre les hiérarchies d’appels ou trouver des modèles difficiles à lire dans la sortie du terminal.

line_profiler : détail ligne par ligne

line_profiler donne des détails au niveau de la fonction au niveau de la ligne individuelle. Il est plus lent que cProfile, mais il montre exactement quelle ligne est chère.

from line_profiler import LineProfiler

lp = LineProfiler()
lp.add_function(my_heavy_function)
lp.enable_by_count()

# Run your code
result = equation.solve(var=phi)
lp.print_stats()

line_profiler est le meilleur pour une enquête approfondie sur une fonction après que vous sachiez déjà quelle partie du programme est lente.

Goulots d’étranglement communs de solveurs PDE

Lorsque vous profilez des solveurs PDE basés sur Python tels que Fipy, Fenics ou des implémentations personnalisées, plusieurs goulots d’étranglement apparaissent souvent.

1. Frais généraux d’assemblage de matrice

L’assemblage matriciel est l’une des surprises les plus courantes. De nombreux chercheurs s’attendent à ce que le solveur linéaire domine le temps d’exécution. Au lieu de cela, le code qui construit la matrice peut prendre plus de temps que la résolution elle-même.

Dans FIPY, la phase equation.prepare() peut consommer une part importante du temps d’exécution avant la fin de equation.solve(). Si la phase de préparation domine, le goulot d’étranglement peut être dans la construction d’équations au niveau de Python plutôt que dans le solveur numérique.

Les stratégies de correction possibles incluent :

  • Opérations par lots au lieu de boucler les variables de maillage en Python Pure.
  • Utilisez des termes sources vectorisées et évitez les boucles Python.
  • Considérez des équations entièrement implicites lorsque des termes sources explicites forcent de petits ajustements de pas de temps fréquents.

2. Performance du solveur linéaire

Lorsque l’assemblage matriciel n’est pas le problème principal, le solveur linéaire peut l’être. Les délégués à l’étape equation.solve() travaillent dans un backend tel que scipy.sparse.linalg, Trilinos, PySparse ou PyAMG.

Si vous passez à une accélération GPU ou à un autre backend, vérifiez où se trouve la surcharge de série. Un solveur peut sembler rapide isolément, mais perd toujours du temps lors de la construction de la matrice ou du transfert de données.

Les stratégies de correction possibles incluent :

  • Testez différents backends de solveur en fonction de l’échelle du problème.
  • Utilisez les préconditionneurs le cas échéant.
  • Comparez le temps total de construction et de résolution, pas seulement le temps d’itération du solveur.

3. Modèles d’allocation de mémoire

Le collecteur et l’allocateur de Python peuvent dominer le runtime si le code crée et rejette de manière répétée de grands tableaux. Ceci est courant dans les boucles de temps, les solveurs itératifs et les opérations de maillage avec des structures de données fragmentées.

Les problèmes d’allocation de mémoire apparaissent souvent dans :

  • Solveurs itératifs avec vecteurs temporaires.
  • Boucles de temps qui réallouent des tableaux à chaque étape.
  • Opérations de maillage qui créent des structures intermédiaires fragmentées.

Les stratégies de correction possibles incluent :

  • Pré-allouer des tableaux avant les boucles temporelles.
  • Utilisez tracemalloc pour identifier les hotspots d’allocation.
  • Utilisez scalene pour un profilage plus large de la mémoire.
  • Évitez de créer de nouveaux tableaux dans les boucles internes lorsque des tampons réutilisables sont possibles.

4. Au-dessus de la boucle de niveau Python

Python est rapide lorsque le travail est poussé dans des opérations vectorisées de NumPy. Il est lent lorsque vous bouclez sur des objets Python cellule par cellule. Si votre discrétisation passe par des cellules en Python pur, vous risquez de perdre des performances significatives par rapport aux alternatives vectorisées.

# Slow: Python-level loop
for cell in mesh.cells:
    value[cell] = compute_stencil(cell)

# Fast: Vectorized
stencils = compute_stencils(mesh)
values = apply_stencil(stencils)

Les stratégies de correction possibles incluent :

  • Déplacez les boucles dans des extensions compilées avec des outils tels que Cython ou Numba.
  • Utilisez les opérations vectorisées de Fipy au lieu d’une itération au niveau Python.
  • Profil d’abord, car parfois la boucle n’est pas le véritable goulot d’étranglement.

5. Conversion d’E/S et de données

Les goulots d’étranglement d’E/S peuvent rester cachés jusqu’à ce que le flux de travail s’adapte. Si le code convertit des données de maillage, écrit des résultats intermédiaires ou lit des fichiers de paramètres volumineux à plusieurs reprises, les E/S peuvent dominer l’exécution.

# This might look fast in isolation
start = time.time()
import numpy as np
data = np.load('large_mesh_data.npy')
print(f"Loading took {time.time() - start:.3f}s")  # Outputs: 0.001s

# But if you do this inside a loop 1000 times: 1000x overhead

La clé est de profiler le flux de travail complet, pas seulement le noyau numérique. Le chargement, la conversion et l’écriture répétés peuvent prendre plus de temps que prévu.

Un exemple de profilage pratique

Voici un moyen pratique de profiler une simulation FIPY.

import cProfile
import pstats
import fipy as fp

# Create a simple diffusion problem
nx = 100
dx = 0.01
mesh = fp.GridMesh(nx, dx)
var = fp.CellVariable(name="phi", mesh=mesh)

# Set up equation
equation = fp.TransientDiffusionTerm(var)

# Profile the solve
cProfile.run('equation.solve(var=var, dt=0.001)', 'diffusion_profile.prof')

# Analyze
with pstats.Stats('diffusion_profile.prof') as stats:
    stats.sort_stats('cumulative')  # or 'time', 'ncalls', etc.
    stats.print_stats(20)  # Top 20 functions

Ouvrez ensuite le profil dans SnakeViz :

$ snakeviz diffusion_profile.prof

Le rapport visuel peut afficher :

  • Quelles branches de fonction prennent le plus de temps.
  • Que le goulot d’étranglement soit à l’intérieur equation.solve() ou dans l’étape de préparation.
  • Combien de temps est passé dans Numpy par rapport au code de niveau Python.

Dans FIPY, l’étape de préparation montre souvent qu’une grande partie du temps est consacrée à la construction de la matrice clairsemée et à l’application des conditions aux limites, et non dans la résolution linéaire elle-même. C’est pourquoi l’optimisation du solveur peut manquer le véritable goulot d’étranglement.

Profilage à grande échelle : lorsque votre simulation prend des heures

Le plus grand défi avec le profilage scientifique n’est pas toujours l’outil. C’est le temps d’exécution. Certaines simulations durent des heures et le profilage de la totalité de la simulation de production peut produire une énorme production.

Les simulations longues nécessitent une stratégie de profilage plus minutieuse.

La solution de contournement : profiler un cas réduit

  1. Réduisez la résolution du maillage ou le nombre de pas de temps.
  2. Exécutez le profileur sur ce petit cas.
  3. Recherchez des modèles structurels dans lesquels les opérations dominent proportionnellement.
  4. Extrapolez soigneusement, car les proportions peuvent être valables même lorsque les temps absolus changent.

Il y a une mise en garde. Les petits cas peuvent cacher les goulots d’étranglement qui n’apparaissent qu’à plus grande échelle. Ceux-ci peuvent inclure une surcharge de communication dans des solveurs parallèles, des erreurs de cache sur de grands réseaux ou une pression de mémoire provenant de représentations intermédiaires denses.

La solution de contournement : profiler une tranche représentative

Si vous exécutez une simulation transitoire avec 10 000 pas de temps, vous n’avez pas toujours besoin de profiler l’ensemble de l’exécution. A la place :

  • Profilez uniquement les 100 premières étapes.
  • Exécutez suffisamment d’itérations afin que les frais généraux de profileur soient faibles par rapport au travail réel.
  • Vérifiez si la répartition du temps reste cohérente à mesure que le nombre d’étapes augmente.

Choisir le profileur adapté à votre cas d’utilisation

Scénario Outil recommandé Pourquoi
Vérification rapide, pas d’installation cProfile Intégré, zéro installation, toujours disponible
Code de production, ne peut pas redémarrer py-spy Peut s’attacher à un processus en cours d’exécution avec une surcharge minimale
Profilage du GPU, du CPU et de la mémoire scalene Tout-en-un, compatible avec le GPU, le profilage au niveau de la ligne
Présentation des résultats aux collaborateurs cProfile + SnakeViz Visuel, interactif et plus facile à expliquer
Plongée profonde dans une seule fonction line_profiler Détail ligne par ligne
Modèles d’allocation de mémoire scalene ou tracemalloc Suivi des allocations, des fuites et de la fragmentation
Simulation de longue durée py-spy Plus réduit cProfile Faible échantillonnage des frais généraux et représentatif

Ce qu’il faut éviter : les erreurs de profilage courantes

Erreur 1 : Profilage d’un problème trop faible. Un maillage 10 × 10 qui se termine en moins d’une seconde ne révélera pas les goulots d’étranglement qui apparaissent à l’échelle de 1 000 × 1 000. profil à une taille représentative lorsque cela est possible.

Erreur 2 : Optimisation avant le profilage. Si vous modifiez des solveurs, ajoutez du parallélisme ou réécrivez des boucles avant de collecter des données de profil, vous devinez.

Erreur 3 : Ignorer la phase de préparation. De nombreux chercheurs ne profilent que equation.solve(), mais la préparation peut prendre plus de temps que la solution. Profilez le pipeline complet.

Erreur 4 : Confondre le plus rapidement isolé et le plus rapide dans l’ensemble. Un solveur avec moins d’itérations peut encore être plus lent s’il nécessite un assemblage de matrice ou une conversion de données coûteux.

Erreur 5 : Profilage une seule fois. Profil Avant l’optimisation, effectuez une modification, puis à nouveau profil. Vérifiez que le goulot d’étranglement est réellement déplacé ou amélioré.

Références internes

Résumé : Le bon ordre est important

Le profilage des performances ne consiste pas à trouver l’astuce la plus rapide. C’est une question de discipline.

  1. mesurer. Exécutez cProfile, py-spy ou scalene sur des tailles de problème représentatives.
  2. Diagnostiquer. Lisez attentivement les résultats. La fonction top n’est pas toujours le véritable goulot d’étranglement.
  3. traiter. Fixez ce que vous avez mesuré, pas ce que vous pensiez.
  4. mesurer à nouveau. Vérifiez que votre correctif vous a réellement aidé.

L’aperçu le plus précieux du profilage est simple : la partie la plus lente de votre code est rarement la partie que vous pensiez lente. Une fois que vous avez des données, l’optimisation cesse d’être une hypothèse et commence à être ingénierie.

Guides connexes