David Larlet : artisan, contributeur et citoyen.


Archives du site biologeek.com. Publications récentes.

Benchmarks map, filter vs. list-comprehensions

vignette

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.

Articles peut-être en rapport


Commentaires

Tarek le 26/10/2006 :

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

JS le 26/10/2006 :

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 ???

sebsauvage le 01/12/2006 :

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.

xfeeder le 29/06/2010 :

les list-comprehensions sont toujours plus rapides que map ou filter avec Python 2.4.