Avant tout, il est nécessaire d'avoir une mesure du temps d'exécution d'un programme. Le moyen le plus simple est d'utiliser la commande 'time'. J'emploie cette commande avec l'option '-p' qui permet un affichage en seconde.
exemple:
time -p python programm1.py
Pour avoir le détail sur les fonctions qui consomment le plus en terme de temps d'exécution, il faut réaliser une opératoin de 'profilage'. Le module cProfile est mon préféré car il s'utilise de manière externe sans modification du source et il est déjà installé.
La commande pour l'activer est la suivante:
python -m cProfile -o eg.prof programme1.py
L'option '-o' est suivie du nom de fichier où seront stocké les statistiques. L'option -s vient en exclusion avec l'option -oo , l'option 's' comme 'sort' affecte un critere de tri : exemple -s cumtime pour trier sur le temps cumulé.
Exemple de sortie:
28917791 function calls (28917181 primitive calls) in 376.750 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
66 0.000 0.000 0.000 0.000 :102(release)
59 0.000 0.000 0.000 0.000 :142(__init__)
59 0.000 0.000 0.002 0.000 :146(__enter__)
59 0.000 0.000 0.001 0.000 :153(__exit__)
66 0.001 0.000 0.001 0.000 :159(_get_module_lock)
Cette sortie n'est pas toujours facile à lire aussi j'utilise une interface graphique Kcachegrind (ou qcachegring sur MacoSX)
L'installation de qcachefrind se fait par:
brew install qcqchegrind
Puis
brew install graphviz
Il reste à convertir un fichier cProfile au format callgrind avec la commande:
pyprof2calltree -i eg.prof -o ant2.grind
(avec -i pour le fichier en entrée, l'option -o pour le fichier en sortie )
Le travail d'optimisation peut commencer en tenant compte de deux axes:
Le temps d exécution d'une méthode (en seconde ou en %) et le nombre d'appel à cette fonction
Dans cet exemple la fonction recherche_conditionC3 est appelée 26524 fois et représente un coût de 85%.
La fonction avant optimisation est comme ceci :
def recherche_conditionC3(self, cle,date):
resultat = 'KO'
ligne=[]
for item in self.tableC3:
if item[0] == cle:
if date >= item[1]:
ligne = item
resultat = 'OK'
if item[0] > cle:
break
return(resultat,ligne)
Il est possible de remplacer une recherche systématique par un pattern de memoization: c'est à dire un type de cache.
....
clecache = cle + date.strftime('%m-%d-%Y')
if clecache in C3taux.dictC3taux:
#pdb.set_trace()
return('OK',C3taux.dictC3taux[clecache])
sinon : continuer dans la méthode.
Avec comme résultat: un cout de moins de 1%
Après une série d'optimisation, de 260 secondes on tombe à 80 secondes.
Ces techniques n'empêchent pas d'avoir une réflexion préalable sur une conception capable d'encaisser des montées en charge.
L'outil qcachegrind propose des vues sur les arbres d'appel des méthodes