Benchmarks map, filter vs. list-comprehensions

Tags associés : , , posté le 25 october 2006


Logo associé au billet intitulé Benchmarks map, filter vs. list-comprehensions

Je viens de tomber sur les snyppets de Seb Sauvage (site que j'apprécie beaucoup par ailleurs) et il y a une phrase qui m'a interpellé sur le paragraphe consacré à zip, map, filter et aux list-comprehensions :

Except that {map|filter} is faster. (than list-comprehensions)

Ni une, ni deux, je récupère l'article de Tarek qui est très bon et qui comporte une fonction testant la durée d'execution des fonctions pour pouvoir comparer. J'avais déjà essayé d'autres fonctions mais autant innover un peu.

Allons-y donc avec le décorateur suivant :

from test import pystone
import time

kPs = 1000
TOLERANCE = 0.5 * kPs

def mesure_pystone():
    pystone.pystones(loops=pystone.LOOPS)
    pystone.pystones(loops=pystone.LOOPS)
    return pystone.pystones(loops=pystone.LOOPS)

def timedtest(function, local_pystones=mesure_pystone()):
    """ Decorator to measure execution time in pystones """
    def wrapper(*args, **kw):
        all = []
        for i in range(3):
            start_time = time.time()
            try:
                res = function(*args, **kw)
            finally:
                total_time = time.time() - start_time
                if total_time == 0:
                    temps = 0
                else:
                    ratio = local_pystones[0] / local_pystones[1]
                    temps = total_time / ratio
                all.append(temps)
        print '%d pystones' % min(all)
        return res
    return wrapper

J'ai testé les fonctions suivantes qui ne sont pas les mêmes que les exemples de Seb mais qui retournent (elles, merci Haypo :-)) le même résultat :

@timedtest
def map_without_list_comprehension():
    for i in range(10000):
        map(abs, [-5,7,-12])

@timedtest
def map_with_list_comprehension():
    for i in range(10000):
        [abs(i) for i in [-5,7,-12]]

print '=== map vs. list-comprehension ==='
print 'map without list-comprehension:'
map_without_list_comprehension()
print 'map with list-comprehension:'
map_with_list_comprehension()

@timedtest
def filter_without_list_comprehension():
    for i in range(10000):
        filter(abs, [-5,7,0,-12] )

@timedtest
def filter_with_list_comprehension():
    for i in range(10000):
        [i for i in [-5,7,0,-12] if i]

print '=== filter vs. list-comprehension ==='
print 'filter without list-comprehension:'
filter_without_list_comprehension()
print 'filter with list-comprehension:'
filter_with_list_comprehension()

Et voila les résultats obtenus :

=== map vs. list-comprehension ===
map without list-comprehension:
638 pystones
map with list-comprehension:
534 pystones
=== filter vs. list-comprehension ===
filter without list-comprehension:
612 pystones
filter with list-comprehension:
550 pystones

Il me semble que c'est sans appel : les list-comprehensions sont toujours plus rapides que map ou filter avec Python 2.4. Il faudrait tester tout ça avec Python 2.5 mais je pense que les résultats seraient encore plus significatifs.

3 Commentaires

Le même script sous Python 2.5 :

tziade@dabox:~$ python test2.py
=== map vs. list-comprehension ===
map without list-comprehension:
707 pystones
map with list-comprehension:
634 pystones
=== filter vs. list-comprehension ===
filter without list-comprehension:
687 pystones
filter with list-comprehension:
634 pystones

1 | Tarek, le 26 October 2006 à 03h

Bon, j'ai rien compris au map et list-comprehension, faudrait que je regarde ca de plus près...

Par contre, t'es sur #python-fr ???

2 | JS, le 26 October 2006 à 09h

Hello !

Tu as oublié qu'avec de si petites données de test, l'overhead d'appel aux fonction devient non négligeable.

J'ai refait les tests avec de plus grands ensembles de données et je maintien ce que j'ai dit:
map et filter sont plus rapide que list comprehension (sauf quand on combine map et filter).

Voici le fichier de test:
sebsauvage.net/python/sny...

Et les résultats:
===== map_without_list_comprehension =====
105 function calls in 0.231 CPU seconds
===== map_with_list_comprehension =====
1000005 function calls in 6.941 CPU seconds

===== filter_without_list_comprehension =====
105 function calls in 0.074 CPU seconds
===== filter_with_list_comprehension =====
1000005 function calls in 6.889 CPU seconds

C'est sans appel: map et filter sont nettement plus rapides.
(Dans notre exemple: d'un facteur x30 pour map, et d'un facteur x93 pour filter)


...sauf quand on doit les combiner:
===== mapfilter_without_list_comprehension =====
2042605 function calls in 13.935 CPU seconds
===== mapfilter_with_list_comprehension =====
521205 function calls in 3.795 CPU seconds

Là la list comprehension est plus rapide.


Donc je ne me suis pas trompé, mais c'est à mitiger. :-)
Il vaut mieux profiler le code.

3 | sebsauvage, le 1 December 2006 à 16h

Ajouter un commentaire


Billets contextuels

Bonnes pratiques et astuces Python

Logo associé au billet intitulé Bonnes pratiques et astuces Python

Ça faisait un moment que je n'avais pas parlé des bonnes pratiques Python mais l'approche de Pycon fr (où je présenterai Django : le pourquoi et le comment le 18 mai), l'événement Python incontournable avec un programme des ...

Python : lisibilité vs simplicité

Logo associé au billet intitulé Python : lisibilité vs simplicité

Le programmeur est fainéant. C'est ainsi. S'il ne l'était pas, il n'essayerait pas de s'aider d'un ordinateur. Du coup il évite dans la mesure du possible de s'encombrer de variables trop longues, ou ...

Principales nouveautés dans Python 2.5

Logo associé au billet intitulé Principales nouveautés dans Python 2.5

Je m'y prend un peu à l'avance (la sortie est prévue pour septembre 2006) mais Guido a apparement fait quelques annonces lors de Pycon qui viennent s'ajouter aux PEPs approuvés sur la page officielle. J'essayerais ...


© 2004-2010 David Larlet - Licence (presque) libre - Site enfin propulsé par Django et hébergé par Typhon.