OVH Cloud OVH Cloud

map+lambda VS for...in

11 réponses
Avatar
SL
Bonjour,

suite au post de Christophe du 10/03 18:58,
je me suis amuse a comparer les perf de map+lambda et for...in
avec le script suivant :

"""
import time

start, end =0 , 0
start = time.time()
[a for a in range(10**6)]
end = time.time()
print "for .. in\t" , end - start

start, end = 0 , 0
start = time.time()
map(lambda a : a, range(10**6))
end = time.time()
print "map + lambda\t" , end - start
"""

qui me donne ce genre de resultat :
for .. in 0.858999967575
map + lambda 0.594000101089

comme quoi map+lambda est sensiblement plus rapide pour parcourir et
traiter une liste.

super... mais comment cela se fait il ???
j'aurais plutot misé sur for...in, vu qu'il n'y a pas d'appel de
fonction a chaque iteration... :-?

merci d'avance pour vos explications,

10 réponses

1 2
Avatar
Eric Deveaud
SL wrote:
Bonjour,

suite au post de Christophe du 10/03 18:58,
je me suis amuse a comparer les perf de map+lambda et for...in
avec le script suivant :

comme quoi map+lambda est sensiblement plus rapide pour parcourir et
traiter une liste.

super... mais comment cela se fait il ???
j'aurais plutot misé sur for...in, vu qu'il n'y a pas d'appel de
fonction a chaque iteration... :-?


sais tu comment fonctionne for in ???

Eric


--
Et tes réflexions virtuelles ne t'ont pas amenées à penser que peut être
l'erreur pouvait eventuellement (...), mélé à une conjonction d'Alpha du
Centaure et du moulin à café de ma grand-mère que TU t'y prenais mal ?
JF in Guide du Macounet Pervers : Ne pas jeter le manche après la cognée

Avatar
Hervé Cauwelier
SL wrote:
comme quoi map+lambda est sensiblement plus rapide pour parcourir et
traiter une liste.

super... mais comment cela se fait il ???


Map est implémenté et exécuté en code C, pas interprété en Python. Les «
comprehensive lists » (je ne connais pas de traduction) sont un peu plus
rapides que les boucles classiques. Le code est sans doute plus
facilement optimisable.

j'aurais plutot misé sur for...in, vu qu'il n'y a pas d'appel de
fonction a chaque iteration... :-?


Tu ne fais pas plus appel à range dans un cas que dans l'autre.
D'ailleurs Python n'a besoin de ne le calculer qu'une fois, serait-ce
une constante littérale ?

Ton bench ne me paraît pas des plus fiables quoiqu'il arrive.

--
Hervé Cauwelier
http://www.oursours.net/

Avatar
Yermat
Bonjour,

suite au post de Christophe du 10/03 18:58,
je me suis amuse a comparer les perf de map+lambda et for...in
avec le script suivant :


Je ne sais pas comment cela fonctionne en interne mais les résultats
sont à relativiser avec les optimisations récentes :

python23 testPerf.py
for .. in 1.22099995613

map + lambda 1.03200006485
for .. in xrange 1.07100009918
map + lambda xrange 0.911999940872

python2.4 testPerf.py
for .. in 0.641000032425

map + lambda 1.03200006485
for .. in xrange 0.520999908447
map + lambda xrange 0.921000003815

Bref, encore une fois, prenez ce que vous préférez lire... ;-)

N'oubliez pas non plus les "generator expressions" avec python 2.4.
(http://www.python.org/peps/pep-0289.html)

l = (a for a in range(10**6))
for a in l:
pass

Ce qui donne chez moi :
(for .. in) 0.671000003815

Pour les curieux qui ce demande si psyco peut quelque chose ici, réponse
non sauf si c'est dans une fonction et avec le for ... in :

python23 testPerf.py
# sans fonction

for .. in 1.42199993134
map + lambda 2.09300017357
for .. in xrange 1.33200001717
map + lambda xrange 1.96299982071

python23 testPerf.py
# dans une fonction

for .. in 0.761999845505
map + lambda 2.58300018311
for .. in xrange 0.780999898911
map + lambda xrange 2.57400012016

Conclusion :
Il n'y en a pas !

--
Yermat

Avatar
Hervé Cauwelier
Yermat wrote:
Je ne sais pas comment cela fonctionne en interne mais les résultats
sont à relativiser avec les optimisations récentes :

python23 testPerf.py
for .. in 1.22099995613

map + lambda 1.03200006485
for .. in xrange 1.07100009918
map + lambda xrange 0.911999940872

python2.4 testPerf.py
for .. in 0.641000032425

map + lambda 1.03200006485
for .. in xrange 0.520999908447
map + lambda xrange 0.921000003815


Il me semble même que Guido n'aime pas (plus ?) les fonction heu...
fonctionneles, commme map. On comprend donc qu'ils bossent à mort sur
les optimisations de boucle pour s'en passer. :-)

--
Hervé Cauwelier
http://www.oursours.net/


Avatar
F. Petitjean
Bonjour,

suite au post de Christophe du 10/03 18:58,
je me suis amuse a comparer les perf de map+lambda et for...in
avec le script suivant :

"""
import time
Oulala on est mal parti


pour tester les performances d'une fonction utilisez timeit.py

start, end =0 , 0
start = time.time()
[a for a in range(10**6)]
Là il y a création d'une liste d'un million d'entiers successifs

puis utlisation du mécanisme "list compréhension" d'une nouvelle liste
d'un million d'entiers
end = time.time()
print "for .. int" , end - start

start, end = 0 , 0
start = time.time()
map(lambda a : a, range(10**6))
Ici la liste est créée par map (map codé en C mais passage par un appel

de fonction anonyme en python à chaque fois)
end = time.time()
print "map + lambdat" , end - start
"""

qui me donne ce genre de resultat :
for .. in 0.858999967575
map + lambda 0.594000101089

comme quoi map+lambda est sensiblement plus rapide pour parcourir et
traiter une liste.
Comme quoi en utilisant les mauvais outils et en ne sachant pas ce que

l'on compte on arrive à des résultats faux (voir à la fin)

super... mais comment cela se fait il ???
j'aurais plutot misé sur for...in, vu qu'il n'y a pas d'appel de
fonction a chaque iteration... :-?

merci d'avance pour vos explications,


Essais avec python2.4 sur un pentium III biprocesseur 1400MHz

map + lambda :
python2.4 -m timeit 'map(lambda a:a, range(10**6))'
10 loops, best of 3: 943 msec per loop

on peut améliorer les choses en ne créant pas une liste de 1 million
d'entiers :
python2.4 -m timeit 'map(lambda a:a, xrange(10**6))'
10 loops, best of 3: 794 msec per loop

Solution avec for ... in liste compréhension
python2.4 -m timeit -s 'grosR = range(10**6)' '[ i for i in grosR]'
10 loops, best of 3: 330 msec per loop
Remarquez que c'est nettement plus rapide que de passer par un appel de
fonction à chaque fois. (contrairement à vos résultats)
python2.4 -m timeit -s 'grosR = range(10**6)' '[ None for i in grosR]'
10 loops, best of 3: 310 msec per loop
on voit ici que c'est la création d'une liste qui prend le plus clair du
temps : réallocation mémoire pour stocker 1 million d'items.

python2.4 -m timeit -s 'grosR = xrange(10**6)' '[ i for i in grosR]'
10 loops, best of 3: 348 msec per loop
Le fait que xrange donne un résultat moins bon est très symptomatique :
la liste compréhension ne peut pas connaitre la taille de la liste à
générer et donc la gestion mémoire est pénalisée.
Les écarts sont tout de même faibles puisque :
python2.4 -m timeit -s 'grosR = xrange(10**6)' '[ None for i in grosR]'
10 loops, best of 3: 293 msec per loop
Avec xrange + en laissant tomber immédiatement les entiers créés on
arrive à passer en tête (courte tête)

Si vous voulez tester la rapidité de for .. in il faut écrire un bloc
"pass" :
python2.4 -m timeit -s 'grosR = range(10**6)' 'for i in grosR: pass'
10 loops, best of 3: 136 msec per loop
Il me semble que map + lambda est un peu largué :)
python2.4 -m timeit -s 'grosR = xrange(10**6)' 'for i in grosR: pass'
10 loops, best of 3: 124 msec per loop

Tentaive de sauver la face pour map : (par rapport à une list comp)
python2.4 -m timeit -s 'grosR = xrange(10**6)' 'map(int, grosR)'
10 loops, best of 3: 685 msec per loop
python2.4 -m timeit -s 'grosR = range(10**6)' 'map(int, grosR)'
10 loops, best of 3: 645 msec per loop
L'objectif est loin d'être atteint!

Il y a eu récemment sur comp.lang.python je crois un message donnant la
manière la plus rapide de créer une liste (dont on connaît le nombre
d'éléments souhaités) :
python2.4 -m timeit '[ None] * 10**6'
10 loops, best of 3: 46.1 msec per loop
Comme par hasard c'est la manière la plus simple à écrire.
Le champion ici:
python2.4 -m timeit 'lst=[ None] * 10**6'
10 loops, best of 3: 44.9 msec per loop


Vous pouvez aussi comparer les performances d'accès de liste range() ou
itérateur xrange() avec les itérateurs de itertools (boucle for vide) :
python2.4 -m timeit -s 'from itertools import islice, count' 'for i in
islice(count(), 10**6): pass'
10 loops, best of 3: 145 msec per loop
ce qui semble honnête, sachant qu'on met en oeuvre deux itérateurs.

En conclusion :
- pour tester la performance, utilisez le module timeit
- la création d'une grande liste n'est pas gratuite
- les appels de fonction sont coûteux en python ce qui handicape "map"
dans un test de performance d'itération (mais "map" fait beaucoup plus
que les autres solutions)
- les boucles for sont relativement optimisées et les list
comprehesion sont optimisées : moins de trois fois plus lente qu'une
boucle vide.
- le module itertools n'est pas à négliger (possibilité de limiter la
place mémoire demandée).

Avatar
SL
Eric Deveaud wrote:
sais tu comment fonctionne for in ???

Eric


non... mais je veux bien de l'info sur le sujet :)


SL

Avatar
SL
Hervé Cauwelier wrote:
SL wrote:

...

j'aurais plutot misé sur for...in, vu qu'il n'y a pas d'appel de
fonction a chaque iteration... :-?



Tu ne fais pas plus appel à range dans un cas que dans l'autre.
D'ailleurs Python n'a besoin de ne le calculer qu'une fois, serait-ce
une constante littérale ?

je fais reference a la fonction lambda ... pas a la fonction range.


Ton bench ne me paraît pas des plus fiables quoiqu'il arrive.



tout a fait d'accord,
cela dit, mon intention n'est pas de produire un benchmark...

tcho


Avatar
SL
Yermat wrote:

Je ne sais pas comment cela fonctionne en interne mais les résultats
sont à relativiser avec les optimisations récentes :

python23 testPerf.py
for .. in 1.22099995613

map + lambda 1.03200006485
for .. in xrange 1.07100009918
map + lambda xrange 0.911999940872

python2.4 testPerf.py
for .. in 0.641000032425

map + lambda 1.03200006485
for .. in xrange 0.520999908447
map + lambda xrange 0.921000003815

en effet...

tu l'aura devine, j'ai fait mon test sur python 2.3.3,

Bref, encore une fois, prenez ce que vous préférez lire... ;-)

N'oubliez pas non plus les "generator expressions" avec python 2.4.
(http://www.python.org/peps/pep-0289.html)

merci pour l'info,


Pour les curieux qui ce demande si psyco peut quelque chose ici, réponse
non sauf si c'est dans une fonction et avec le for ... in :

kesako psyco ?


python23 testPerf.py
# sans fonction

for .. in 1.42199993134
map + lambda 2.09300017357
for .. in xrange 1.33200001717
map + lambda xrange 1.96299982071

python23 testPerf.py
# dans une fonction

for .. in 0.761999845505
map + lambda 2.58300018311
for .. in xrange 0.780999898911
map + lambda xrange 2.57400012016

je ne comprends pas... ?

quelle fonction ?
Conclusion :
Il n'y en a pas !

ca ok, je comprends :)



Avatar
SL
F. Petitjean wrote:

import time


Oulala on est mal parti
:)

Là il y a création d'une liste d'un million d'entiers successifs
puis utlisation du mécanisme "list compréhension" d'une nouvelle liste
d'un million d'entiers

ok, bon faut que je comprehende cette histoire de list comprehension...


comme quoi map+lambda est sensiblement plus rapide pour parcourir et
traiter une liste.


Comme quoi en utilisant les mauvais outils et en ne sachant pas ce que
l'on compte on arrive à des résultats faux (voir à la fin)

c'est pas ce que je comprends, j'ai fait mon 'test' avec python 2.3.3.


si je le refais avec timeit

"""
import timeit

t = timeit.Timer('map(lambda a : a, range(10**6))')
print "map + lambdat" , t.timeit(1)

t = timeit.Timer('[a for a in range(10**6)]')
print "for .. int" , t.timeit(1)
"""

j'obtiens :
map + lambda 0.583535616957
for .. in 0.746799383721

ce qui confirme mon resultat :)


Essais avec python2.4 sur un pentium III biprocesseur 1400MHz

j'aime bien le 'UN' pentium bi-pro ;)

cela dit, les pIII n'y sont pour rien...
apparemment ce sont des optimisations de python2.4 qui changent la donne

Si vous voulez tester la rapidité de for .. in il faut écrire un bloc
"pass" :
python2.4 -m timeit -s 'grosR = range(10**6)' 'for i in grosR: pass'
10 loops, best of 3: 136 msec per loop
Il me semble que map + lambda est un peu largué :)
en effet ... mais on ne teste plus du tout la meme chose...


Tentaive de sauver la face pour map : (par rapport à une list comp)
python2.4 -m timeit -s 'grosR = xrange(10**6)' 'map(int, grosR)'
10 loops, best of 3: 685 msec per loop
python2.4 -m timeit -s 'grosR = range(10**6)' 'map(int, grosR)'
10 loops, best of 3: 645 msec per loop
L'objectif est loin d'être atteint!

ben oui mais la tu fais une conversion int->int en plus.


essaye ca plutot pour voir :
python2.4 -m timeit -s 'grosR = xrange(10**6)' 'map(None, grosR)'
a mon avis ca bat tous les records :)))

et ca :
python2.4 -m timeit -s 'grosR = range(10**6)' 'for i in grosR: int(i)'
on va voit si les optimisations de python2.4 peuvent redorer le blason
de for in ;)

Il y a eu récemment sur comp.lang.python je crois un message donnant la
manière la plus rapide de créer une liste (dont on connaît le nombre
d'éléments souhaités) :
python2.4 -m timeit '[ None] * 10**6'
10 loops, best of 3: 46.1 msec per loop
Comme par hasard c'est la manière la plus simple à écrire.
Le champion ici:
python2.4 -m timeit 'lst=[ None] * 10**6'
10 loops, best of 3: 44.9 msec per loop

le fait d'affecter la liste a une variable nous fait gagner en perf ?


En conclusion :
- pour tester la performance, utilisez le module timeit
- la création d'une grande liste n'est pas gratuite
- les appels de fonction sont coûteux en python ce qui handicape "map"
dans un test de performance d'itération (mais "map" fait beaucoup plus
que les autres solutions)
- les boucles for sont relativement optimisées et les list
comprehesion sont optimisées : cs de trois fois plus lente qu'une
boucle vide.
- le module itertools n'est pas à négliger (possibilité de limiter la
place mémoire demandée).

et merci pour ces conclusions/infos tres interessantes.


tcho


Avatar
Yermat
Yermat wrote:

[...]
Pour les curieux qui ce demande si psyco peut quelque chose ici,
réponse non sauf si c'est dans une fonction et avec le for ... in :

kesako psyco ?



psyco c'est le compilateur "à la volée" ("Just In Time") pour python.
http://psyco.sourceforge.net/

Et donc il ne "compile" que les fonctions il faut donc faire :
"""
import time

import psyco
psyco.full()

def test():
start, end =0 , 0
start = time.time()
[a for a in range(10**6)]
end = time.time()
print "for .. int" , end - start

test()
"""

--
Yermat


1 2