OVH Cloud OVH Cloud

Un petit pb de rapidité

25 réponses
Avatar
jean-michel bain-cornu
Bonjour,
Récemment s'est posé la question de convertir une image grise en 2
niveaux de noir et blanc. Une des solutions consiste à traiter l'image
avec une boucle.
Le pb est que si l'image est grosse (2 mégas pixels dans l'exemple
ci-après), le traitement est long (environ 20 secs sur un PC moyen).
Sachant que la boucle est un bête test du premier octet de chaque groupe
de trois octets, suivis d'une concaténation de chaîne, quelqu'un
connaît-il un moyen d'aller plus vite ?
Merci
jm

# -*- coding: iso-8859-1 -*-
from array import *
print 'début'
grisClair= chr(240)*3
grisFonce= chr(10)*3
data1= array('c',(grisClair+grisFonce)*10**6) # crée un array contenant
un buffer de 2 millions de pixels RVB gris
data2= ''
blanc= chr(255)*3
noir= chr(0)*3
# boucle sur chaque groupe de 3 octets du buffer
for pixind in range(0,len(data1),3):
if ord(data1[pixind]) < 128:
data2=data2+noir
else:
data2=data2+blanc
# data2 est un string contenant un buffer de 2 millions de pixels blancs
ou noirs
print 'fin'

10 réponses

1 2 3
Avatar
bruno at modulix
jean-michel bain-cornu wrote:
Bonjour,
Récemment s'est posé la question de convertir une image grise en 2
niveaux de noir et blanc. Une des solutions consiste à traiter l'image
avec une boucle.
Le pb est que si l'image est grosse (2 mégas pixels dans l'exemple
ci-après), le traitement est long (environ 20 secs sur un PC moyen).
Sachant que la boucle est un bête test du premier octet de chaque groupe
de trois octets, suivis d'une concaténation de chaîne,


*stop*

La concaténation de chaine dans une boucle est un antipattern connu dans
pas mal de langages, dont Python. L'idiome pythonesque est d'utiliser
une liste comme accumulateur, puis d'appeler str.join() dessus.

quelqu'un
connaît-il un moyen d'aller plus vite ?

# -*- coding: iso-8859-1 -*-
from array import *
print 'début'
grisClair= chr(240)*3
grisFonce= chr(10)*3
data1= array('c',(grisClair+grisFonce)*10**6) # crée un array
contenant un buffer de 2 millions de pixels RVB gris
#data2= ''

data2 = []

blanc= chr(255)*3
noir= chr(0)*3
# utiliser le subscripting plutot qu'un if/else

choice = (noir, blanc)

# boucle sur chaque groupe de 3 octets du buffer
# for pixind in range(0,len(data1),3):

# utiliser les slices étendus (si ça fonctionne avec les arrays,
# à vérifier)
for pix in data1[::3]:
# if ord(data1[pixind]) < 128:
# data2Úta2+noir
# else:
# data2Úta2+blanc
# utiliser le subscripting plutot qu'un if/else, suite...
data2.append(choice[ord(pix) < 128])

data2 = ''.join(data2)
# data2 est un string contenant un buffer de 2 millions de pixels blancs
ou noirs


Vu la taille de la liste, il y a peut-être moyen de faire mieux avec des
generateurs/iterateurs. Jette éventuellement un coup d'oeil au module
itertools.

mes 2 centimes...

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in ''.split('@')])"

Avatar
Laurent Pointal
for pixind in range(0,len(data1),3):


Vu la taille des données, un xrange serait pas mal.

Avatar
Laurent Pointal
Script d'origine:
C:dev>python grisaille.py
dÚbut
fin
10.4461868032

Avec le xrange:
C:dev>python grisaille.py
dÚbut
fin
10.1026271279

Avec la liste construite par data2.append() puis jointée à la fin:
C:dev>python grisaille.py
dÚbut
fin
1.95331342865


Je ne suis pas sûr que les générateurs/itérateurs puissent apporter un
gain de temps ensuite - la boucle Python est coûteuse... un petit coup
de Pyrex peut-être.
http://www.cosc.canterbury.ac.nz/~greg/python/Pyrex/



#!/bin/env python
# -*- coding: iso-8859-1 -*-
# file: grisaille.py
import time
from array import *
t = time.clock()
print 'début'
grisClair= chr(240)*3
grisFonce= chr(10)*3
data1= array('c',(grisClair+grisFonce)*10**6) # crée un array
# contenant un buffer de 2 millions de pixels RVB gris
data2= []
blanc= chr(255)*3
noir= chr(0)*3
# boucle sur chaque groupe de 3 octets du buffer
for pixind in xrange(0,len(data1),3):
if ord(data1[pixind]) < 128:
data2.append(noir)
else:
data2.append(blanc)
# data2 est un string contenant un buffer de 2 millions de pixels
# blancs ou noirs
data2 = ''.join(data2)
print 'fin'
print time.clock() - t
Avatar
Eric Deveaud
bruno at modulix wrote:
jean-michel bain-cornu wrote:
Bonjour,
Récemment s'est posé la question de convertir une image grise en 2
niveaux de noir et blanc. Une des solutions consiste à traiter l'image
avec une boucle.
Le pb est que si l'image est grosse (2 mégas pixels dans l'exemple
ci-après), le traitement est long (environ 20 secs sur un PC moyen).
Sachant que la boucle est un bête test du premier octet de chaque groupe
de trois octets, suivis d'une concaténation de chaîne,


*stop*

La concaténation de chaine dans une boucle est un antipattern connu dans
pas mal de langages, dont Python. L'idiome pythonesque est d'utiliser
une liste comme accumulateur, puis d'appeler str.join() dessus.



** Stop 2 **

depuis la 2.4 ce problème est levé. les version préalables de python étaient
affectées, en effet chaque itération entrainait la création d'une nouvelle
instance de string.

Depuis la version 2.4, le code de python ayant été modifié pour ne plus
générer d'instances intermédiaires en cours d'itération, le code pattern que
tu décris est légérement moin rapide que la concaténation itérative de
chaines.

Eric

--
- Papa ! Papa ! PAAAAAAAAApppppppaaaaaaaaa !
- Oui Ben qu'est-ce-qu'il y a ?
- Y'a quelqu'un qui a touché à mon kernel !
+BL in Guide du Macounet Pervers : MOSXS est un long fleuve tranquille+


Avatar
Eric Deveaud
jean-michel bain-cornu wrote:
Bonjour,
Récemment s'est posé la question de convertir une image grise en 2
niveaux de noir et blanc. Une des solutions consiste à traiter l'image
avec une boucle.
Le pb est que si l'image est grosse (2 mégas pixels dans l'exemple
ci-après), le traitement est long (environ 20 secs sur un PC moyen).
Sachant que la boucle est un bête test du premier octet de chaque groupe
de trois octets, suivis d'une concaténation de chaîne, quelqu'un
connaît-il un moyen d'aller plus vite ?


omme le signale Bruno il y a plusieurs points qui peuvent gagner a etre
améliorés
utiliser les slices étendus
utiliser le subscripting

minimiser les appels.

ainsi le simple fait de définir une varaible locale qui pointe sur la
fonction/méthode à utiliser permet d'eviter la recherche dans l'environement
local/global de celle-ci
par exemple definir my_ord = ord et d'utiliser my_ord en lieu et place de la
fonction d'origine permet de gagner qulques pouièmes qui mis bout a bout font
gagner un peu de temps.
NB ceci est a utiliser avec précaution et de préférence correctement documenté
dans le code

utiliser les "list comprehension"

je propose pour illustrer les 4 codes suivants et leur bench respectifs
from array import *
from timeit import Timer

def test1():
# code de l'OP
grisClair= chr(240)*3
grisFonce= chr(10)*3
data1= array('c',(grisClair+grisFonce)*10**5) # OP was 10^6
data2= ''
blanc= chr(255)*3
noir= chr(0)*3
for pixind in range(0,len(data1),3):
if ord(data1[pixind]) < 128:
data2Úta2+noir
else:
data2Úta2+blanc
return data2

def test2():
# prise en compte des suggstion de Bruno
# code pattern pour la concatenation des chaines
# slice etendus
# subscripting
grisClair= chr(240)*3
grisFonce= chr(10)*3
data1= array('c',(grisClair+grisFonce)*10**5) # OP was 10^6
data2= []
blanc= chr(255)*3
noir= chr(0)*3
choice = (noir, blanc)
for pix in data1[::3]:
data2.append(choice[ord(pix) < 128])
return ''.join(data2)


def test3():
# idem ci-dessus snas look ahead
grisClair= chr(240)*3
grisFonce= chr(10)*3
data1= array('c',(grisClair+grisFonce)*10**5) # OP was 10^6
data2= []
blanc= chr(255)*3
noir= chr(0)*3
choice = (noir, blanc)
# astuce pour eviter le look ahead
my_ord = ord
for pix in data1[::3]:
data2.append(choice[my_ordo(pix) < 128])

return ''.join(data2)


def test4():
# idem ci-dessus avec utilisation des list comprehension
grisClair= chr(240)*3
grisFonce= chr(10)*3
data1= array('c',(grisClair+grisFonce)*10**5) # OP was 10^6
blanc= chr(255)*3
noir= chr(0)*3
choice = ( blanc, noir)
# astuce pour eviter le look ahead
o = ord
return [choice[o(pix) < 128] for pix in data1[::3]]


if __name__ == '__main__':


t = Timer('test1()', 'from __main__ import test1')
print "test 1 %f" % t.timeit(1)

t = Timer('test2()', 'from __main__ import test2')
print "test 2 %f" % t.timeit(1)

t = Timer('test3()', 'from __main__ import test3')
print "test 3 %f" % t.timeit(1)

t = Timer('test4()', 'from __main__ import test4')
print "test 4 %f" % t.timeit(1)


de qui donne sur une machine peu chargée
hebus:check/work_dir > python test.py
test 1 171.316480
test 2 0.370707
test 3 0.342549
test 4 0.288745

il y a tout de meme une sacré echelle de différence non ??

et je pense qu'on peu encore faire un peu mieux.


Eric

Avatar
bruno at modulix
Laurent Pointal wrote:
Script d'origine:
C:dev>python grisaille.py
dÚbut
fin
10.4461868032

Avec le xrange:
C:dev>python grisaille.py
dÚbut
fin
10.1026271279


Note qu'il est parfaitement possible d'éviter aussi bien le range que le
xrange (ainsi que l'accès indexé) en utilisant les slices étendus:

for pix in data1[::3]:
...

Avec la liste construite par data2.append() puis jointée à la fin:
C:dev>python grisaille.py
dÚbut
fin
1.95331342865



Laurent, quelle version de Python et quelle plateforme STP ?

cf le post d'Eric - j'ai fait un petit test, et avec python2.4/gentoo
linux sur un amd64 bipro, effectivement (et à ma grande surprise), la
concaténation s'avère un poil plus rapide...

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in ''.split('@')])"

Avatar
Eric Deveaud
bruno at modulix wrote:

cf le post d'Eric - j'ai fait un petit test, et avec python2.4/gentoo
linux sur un amd64 bipro, effectivement (et à ma grande surprise), la
concaténation s'avère un poil plus rapide...


j'avoue aussi que ça m'a surpris quand je l'ai lu dans le bouquin de Tarek
Ziadé, conceptuellement j'ai du mal a visualiser comment ils (les gens de
python) ont fait mais en effet ca tourne un peu plus vite ;-)

Eric

Avatar
Laurent Pointal
Laurent Pointal wrote:
Script d'origine:
C:dev>python grisaille.py
dÚbut
fin
10.4461868032

Avec le xrange:
C:dev>python grisaille.py
dÚbut
fin
10.1026271279


Note qu'il est parfaitement possible d'éviter aussi bien le range que le
xrange (ainsi que l'accès indexé) en utilisant les slices étendus:

for pix in data1[::3]:


Ca semble un peu moins bien:
C:dev>python grisaille.py
dÚbut
fin
1.782926217

Avec la liste construite par data2.append() puis jointée à la fin:
C:dev>python grisaille.py
dÚbut
fin
1.95331342865



Laurent, quelle version de Python et quelle plateforme STP ?


C:dev>python
Python 2.4.2 (#67, Sep 28 2005, 12:41:11) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.

Windows XP, SP1 (le SP2 ne veut pas s'installer - pas eu le temps de
creuser le pb).

Pentium 4, 3.2GHz, 1Go de RAM


cf le post d'Eric - j'ai fait un petit test, et avec python2.4/gentoo
linux sur un amd64 bipro, effectivement (et à ma grande surprise), la
concaténation s'avère un poil plus rapide...



Avatar
Laurent Pointal

Je n'ai pas le même niveau de différence.
Ton script, en repassant en 10**6 pour pouvoir comparer avec les tests
que j'ai fait un peu avant:

C:dev>python grisailles.py
test 1 9.383566
test 2 1.359730
test 3 1.264727
test 4 0.786419
Avatar
Laurent Pointal

Je n'ai pas le même niveau de différence.


Sûrement dû aux améliorations entre le 2.3 et le 2.4 que tu citais.

1 2 3