Optimiser Jeu-de-vie itération sur 80x60 matrice de pixels RVB
-
19-09-2019 - |
Question
Ok, donc j'ai un morceau de code Python qui a vraiment besoin de l'optimisation.
- Il est un jeu de vie itération sur une petite (80x60 pixels) image et extrait les valeurs RVB de celui-ci.
- utilise actuellement imbriqués pour-boucles; Je préfère échanger sur les boucles pour de la fonction plus rapide
map()
c, mais si je fais ce que je ne peux pas comprendre comment je peux obtenir les valeurs x, y, ni les valeurs locales définies hors de la portée des fonctions I « d besoin de définir. - serait en utilisant
map()
être plus vite que ce jeu actuel pour les boucles? Comment pourrais-je l'utiliser et toujours obtenir x, y? - J'utilise actuellement des surfaces Pygame, et je l'ai essayé les modules
surfarray/pixelarray
, mais depuis que je change / obtenir chaque pixel, il est beaucoup plus lent queSurface.get_at()/set_at()
. - En outre, un peu hors de propos ... pensez-vous que cela pourrait être plus rapide si Python n'a pas été parcourait une liste de numéros mais juste incrémenter un numéro, comme dans d'autres langues? Pourquoi ne pas inclure un python ainsi que leur foreach normal () ()?
- La quantité de conditionals il fait probablement des choses plus lent aussi, non? La partie la plus lente est la vérification des voisins (où il construit la liste n) ... Je l'ai remplacé que tralala avec accès tranche sur un tableau 2D, mais il ne fonctionne pas correctement.
Version expurgée du code:
xr = xrange(80)
yr = xrange(60)
# surface is an instance of pygame.Surface
get_at = surface.get_at()
set_at = surface.set_at()
for x in xr:
# ....
for y in yr:
# ...
pixelR = get_at((x,y))[0]
pixelG = get_at((x,y))[1]
pixelB = get_at((x,y))[2]
# ... more complex stuff here which changes R,G,B values independently of each other
set_at((x,y),(pixelR,pixelG,pixelB))
La version complète de la fonction:
# xr, yr = xrange(80), xrange(60)
def live(surface,xr,yr):
randint = random.randint
set_at = surface.set_at
get_at = surface.get_at
perfect = perfectNeighbours #
minN = minNeighbours # All global variables that're defined in a config file.
maxN = maxNeighbours #
pos = actual # actual = (80,60)
n = []
append = n.append
NEIGHBOURS = 0
for y in yr: # going height-first for aesthetic reasons.
decay = randint(1,maxDecay)
growth = randint(1,maxGrowth)
for x in xr:
r, g, b, a = get_at((x,y))
del n[:]
NEIGHBOURS = 0
if x>0 and y>0 and x<pos[0]-1 and y<pos[1]-1:
append(get_at((x-1,y-1))[1])
append(get_at((x+1,y-1))[1])
append(get_at((x,y-1))[1])
append(get_at((x-1,y))[1])
append(get_at((x+1,y))[1])
append(get_at((x-1,y+1))[1])
append(get_at((x+1,y+1))[1])
append(get_at((x,y+1))[1])
for a in n:
if a > 63:
NEIGHBOURS += 1
if NEIGHBOURS == 0 and (r,g,b) == (0,0,0): pass
else:
if NEIGHBOURS < minN or NEIGHBOURS > maxN:
g = 0
b = 0
elif NEIGHBOURS==perfect:
g += growth
if g > 255:
g = 255
b += growth
if b > growth: b = growth
else:
if g > 10: r = g-10
if g > 200: b = g-100
if r > growth: g = r
g -= decay
if g < 0:
g = 0
b = 0
r -= 1
if r < 0:
r = 0
set_at((x,y),(r,g,b))
La solution
Puisque vous lisez et réécriture tous pixel, je pense que vous pouvez obtenir en ne pas utiliser un Surface
la meilleure amélioration de la vitesse.
Je suggère d'abord prendre votre image 80x60 et le convertir en un fichier bitmap simple avec pixels 32 bits. Ensuite, lisez les données de pixels dans un python array
objet. Maintenant, vous pouvez marcher sur l'objet array
, la lecture des valeurs, le calcul de nouvelles valeurs, et les nouvelles valeurs poussant en place avec une vitesse maximale. Lorsque vous avez terminé, enregistrez votre nouvelle image bitmap, puis le convertir en un Surface
.
Vous pouvez également utiliser des pixels 24 bits, mais cela devrait être plus lent. pixels 32 bits signifie un pixel est une valeur entière de 32 bits, ce qui rend la matrice de pixels beaucoup plus facile d'index. 24 bits des pixels emballés signifie que chaque pixel est de 3 octets, ce qui est beaucoup plus ennuyeux pour indexer.
Je crois que vous gagnerez beaucoup plus de vitesse sur cette approche que en essayant d'éviter l'utilisation de for
. Si vous essayez cela, s'il vous plaît poster quelque chose ici pour nous le faire savoir à quel point cela a fonctionné ou n'a pas fait. Bonne chance.
EDIT: Je pensais qu'un array
n'a qu'un seul indice. Je ne sais pas comment vous avez réussi à obtenir deux index pour travailler. Je vous attendais à faire quelque chose comme ceci:
def __i(x, y):
assert(0 <= x < 80)
assert(0 <= y < 60)
i = (y*80 + x) * 4
return i
def red(x, y):
return __a[__i(x, y)]
def green(x, y):
return __a[__i(x, y) + 1]
def blue(x, y):
return __a[__i(x, y) + 2]
def rgb(x, y):
i = __i(x, y)
return __a[i], __a[i + 1], __a[i + 2]
def set_rgb(x, y, r, g, b):
i = __i(x, y)
_a[i] = r
_a[i + 1] = g
_a[i + 2] = b
# example:
r, g, b = rgb(23, 33)
Depuis un array
Python ne peut contenir un seul type, vous souhaitez définir le type de « octet non signé », puis l'index comme je l'ai montré.
Si bien sûr __a
est la variable array
réelle.
Si rien de tout cela est utile, essayez de convertir votre bitmap dans une liste, ou peut-être trois listes. Vous pouvez utiliser des listes imbriquées pour obtenir d'adressage 2D.
J'espère que cela aide. Si ce n'est pas utile, alors je ne comprends pas ce que vous faites; si vous expliquez plus je vais essayer d'améliorer la réponse.
Autres conseils
Qu'est-ce qui rend votre code lent est probablement pas les boucles, ils sont incroyablement rapides.
Que fait votre code ralentit sont le nombre d'appels de fonction. Par exemple
pixelR = get_at((x,y))[0]
pixelG = get_at((x,y))[1]
pixelB = get_at((x,y))[2]
est beaucoup plus lent que (environ 3 fois je suppose)
r, g, b, a = get_at((x,y))
Chaque get_at
, serrures d'appel set_at
la surface, donc il est plus rapide d'accéder directement aux pixels en utilisant les méthodes disponibles. Celui qui est le plus raisonnable semble Surface.get_buffer
.
Utilisation map
ne fonctionne pas dans votre exemple, parce que vous avez besoin des index. Avec aussi peu que 80 et 60 chiffres, il pourrait même être plus rapide d'utiliser range()
au lieu de xrange()
.
map(do_stuff, ((x, y) for x in xrange(80) for y in xrange(60)))
où do_stuff
serait vraisemblablement défini comme ceci:
def do_stuff(coords):
r, g, b, a = get_at(coords)
# ... whatever you need to do with those ...
set_at(coords, (r, g, b))
Vous pouvez également utiliser une compréhension de liste au lieu d'une expression de générateur comme second argument à map
(remplacer ((x, y) ...)
avec [(x, y) ...]
) et utiliser range
au lieu de xrange
. Je dirais qu'il est peu probable d'avoir un effet significatif sur les performances, cependant.
Modifier Notez que gs a certainement raison de la for
boucles ne pas être la chose principale dans le besoin d'optimisation de votre code ... Réduire les appels superflus à get_at
est plus important. En fait, je ne suis pas sûr de remplacer les boucles avec map
va réellement améliorer les performances ici du tout ... Cela dit, je trouve la version map
plus lisible (peut-être à cause de mon fond ... FP), alors là, vous aller de toute façon. ; -)