80x60 RGB ピクセル配列にわたるゲーム・オブ・ライフの反復を最適化する
-
19-09-2019 - |
質問
さて、本当に最適化が必要な Python コードができました。
- これは、小さな (80x60 ピクセル) イメージに対するライフ ゲームの反復であり、そこから RGB 値を抽出します。
- 現在ネストされた for ループを使用しています。より高速にするには、for ループを交換したいと思います
map()
c 関数を使用しますが、それを行うと、x、y 値を取得する方法がわかりません。また、定義する必要がある関数の範囲外で定義されたローカル値もわかりません。 - 使用するだろう
map()
この現在の for ループのセットよりも高速になるでしょうか?これを使用しても x、y を取得するにはどうすればよいでしょうか? - 私は現在 pygame Surfaces を使用しています。
surfarray/pixelarray
モジュールですが、すべてのピクセルを変更/取得しているため、モジュールよりもはるかに遅くなります。Surface.get_at()/set_at()
. - あと、ちょっと関係ないですが…他の言語のように、Python が数値のリストを走査するのではなく、単に数値をインクリメントするだけであれば、これをもっと速くできると思いますか?なぜ Python には foreach() だけでなく通常の for() も含まれていないのでしょうか?
- 条件文の量もおそらく処理を遅くしますよね?最も遅い部分は、近隣ノードのチェックです (ここでリスト n が構築されます)。そのビット全体を 2D 配列のスライス アクセスに置き換えましたが、正しく動作しません。
コードの編集されたバージョン:
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))
関数の完全版:
# 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))
解決
あなたが読んで書き換えされているので、のすべてののピクセル、私はあなたがSurface
を使用しないことにより、最高速度向上を得ることができると思います。
私が最初にあなたの80×60の画像を撮影し、32ビットピクセルとプレーンなビットマップファイルに変換することをお勧めします。次いでパイソン array
のオブジェクトに画素データを読み出します。今、あなたは、値を読み込む新しい値を計算し、最大速度で所定の場所に新しい値を突っつい、array
のオブジェクトの上を歩くことができます。完了したら、あなたの新しいビットマップイメージを保存し、Surface
に変換します。
また、24ビットのピクセルを使用することができますが、それは遅くする必要があります。 32ビットピクセルが一つのピクセルは、インデックスピクセルのアレイがはるかに容易にする1つの32ビット整数値であることを意味します。 24ビットのパックされたピクセルは、各ピクセルは、へのインデックスにはるかに迷惑である3バイトであることを意味します。
私はあなたがfor
の使用を避けるためにしようとするよりも、このアプローチのうち、より多くの速度を得ると確信しています。あなたはこれをしようとすると、私たちはそれが働いていたりしませんでしたどれだけ知っているようにここで何かを投稿してください。幸運ます。
編集:私はarray
は、単一のインデックスを持っていると思いました。私はあなたが動作するように2つのインデックスを得ることができたかどうかはわかりません。私はあなたがこのような何かを期待していた。
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)
Pythonのarray
は、単一のタイプを保持することができますので、、あなたは私が示したように、「符号なしバイト」とタイプして、インデックスを設定することになるでしょう。
どこコース__a
の実際のarray
変数です。
こののどれもが参考にされていない場合は、リストにあなたのビットマップを変換してみてください、またはおそらく3つのリスト。あなたは2Dアドレッシング取得するには、ネストされたリストを使用することができます。
私はこのことができます願っています。それが役に立たない場合は、私はあなたが何をしているか理解していないのです。あなたがより多くを説明する場合、私は答えを改善しようとするでしょう。
他のヒント
ループおそらくあなたのコードが遅いされていない作っている何を、彼らは非常に高速です。
あなたのコードを行って遅くなり、何が関数呼び出しの数です。たとえば、
pixelR = get_at((x,y))[0]
pixelG = get_at((x,y))[1]
pixelB = get_at((x,y))[2]
はの多くのより遅い(約3回、私は推測する)
r, g, b, a = get_at((x,y))
すべてのget_at
は、set_at
コールロック表面は、したがって、それが直接利用可能な方法を用いて、各画素にアクセスするために高速です。最も合理的と思われる一つは Surface.get_buffer
のです。
あなたがインデックスを必要とするので、あなたの例では動作しませんmap
を使用します。 80と60の数字としていくつかではそれも代わりrange()
のxrange()
を使用する方が速いかもしれません。
map(do_stuff, ((x, y) for x in xrange(80) for y in xrange(60)))
do_stuff
が、おそらくそうのように定義されますここで、
def do_stuff(coords):
r, g, b, a = get_at(coords)
# ... whatever you need to do with those ...
set_at(coords, (r, g, b))
あなたは、代わりmap
(((x, y) ...)
と[(x, y) ...]
を置き換え)とrange
の代わりにxrange
を使用する2番目の引数としてジェネレータ式の代わりに、リストの内包表記を使用することができます。でも、私はそれがパフォーマンスに大きな影響を与える可能性が非常に高いではないことを言うと思います。
の編集:注それはgsをfor
程度確かに右である...あなたのコード内の最適化を必要としている主なものであることget_at
がより重要であると余計な通話を減らしませんループします。実際に、私は、あなたはので、ここで、map
とループを交換し、実際にすべてここにパフォーマンスが向上するかどうかわからないんだけど...と言ったので、私は(...恐らくは私のFPの背景)より読みやすいmap
バージョンを見つけますとにかく行きます。 ; - )