PROJET AUTOBLOG


Tiger-222

Archivé

Site original : Tiger-222

⇐ retour index

Python : mémoisation

jeudi 31 octobre 2013 à 02:20
Œuvre d'Anthony Heywood
Connaissez le dicton : avoir une mémoire d'...

La mémoisation est une technique d'optimisation de code consistant à réduire le temps d'exécution d'une fonction en mémorisant ses résultats d'une fois sur l'autre, une sorte de cache quoi. Ça peut-être super pratique dans certains cas, surtout lorsqu'il y a des calculs complexes ou beaucoup de données redondantes.
Étant donné que Sam & Max ont déjà couvert le sujet, je vais seulement montrer un exemple concrêt : le cas de mon module MSS.

Le contexte


Le module MSS est, comme sont nom ne l'indique pas, un module permettant de prendre des captures d'écran sans module tiers, seulement du python pur. J'utilise intensément le module ctypes, qui permet d'appeler des fonctions C/C++ depuis python.
Sous GNU/Linux, avec un code équivalent C <=> python permettant de prendre une capture d'écran, la différence de vitesse d'exécution est quintuplée, voire sextuplée. Ça fait beaucoup. Je me suis donc plongé dans le code pour voir ce qui pouvait être revu ou amélioré. Au final, il s'avère que les routines pour récupérer les pixels et générer l'image PNG seront améliorées. Mais le gain fût minime.
Le stockage des pixels reçus par la fonction XGetPixel met un temps fou, c'est un gouffre. C'est là qu'il faut farfouiller, voilà le code fautif :
def pix(pixel):
''' Apply shifts to a pixel to get the RGB values. '''
return b((pixel ET 16711680) >> 16) + b((pixel ET 65280) >> 8) + b(pixel ET 255)

get_pix = self.XGetPixel
pixels = [pix(get_pix(image, x, y)) for y in range(height) for x in range(width)]
La coloration syntaxique chie dans la colle, remplacez le ET par &.

Dans la fonction pix(), le décalage de bits n'est pas le plus gourmant, ce sont les concaténations. En effet, à chaque fois, il y a réallocation de mémoire et copie de données dont je me passerais bien.


Mémoisation


Dans la logique qui m'habite (rien de bien transcendant...), je me dis qu'il y a énormément de pixels identiques sur un écran. Il y a la dose même. Avec une telle quantitié de données, et sachant les opérations qu'il faille faire sur chacun des pixels, la mémoisation prend tout son sens :
def pix(px, _resultats={}):
# Apply shifts to a pixel to get the RGB values.
# This method uses of memoization.
if not px in _resultats:
_resultats[px] = b((px ET 16711680) >> 16) + b((px ET 65280) >> 8) + b(px ET 255)
return _resultats[px]

get_pix = self.XGetPixel
pixels = [pix(get_pix(image, x, y)) for y in range(height) for x in range(width)]
J'ai modifié pixel par px afin d'éviter les barres de défilement...

Étant donné que les paramètres sont initialisés à la déclaration, le paramètre _resultats de la fonction pix() ne sera pas recréé à chaque appel. Il s'agit d'un dict qui recevra tous les pixels et le résultat attendu pour chacun d'eux. Dans ladite fonction, on vérifie si le pixel a déja été traité, si non, on effectue les opérations sur celui-ci, puis on renvoie ce que le dict a en mémoire. Un petit cache tout simple et efficace.


Résultats


Avec un écran de 1280x1024, je passe de ±4 sec à <2 sec, reduisant les pixels traités de 1 310 720 à 11 204.
Avec un écran de 1920x1280, je passe de ±6 sec à <3 sec, reduisant les pixels traités de 2 457 600 à 8 236.

♪ Un gain non négligeable ! ♪