优化80x60 RGB像素阵列的生命周期迭代

时间:2010-01-08 22:27:40

标签: python arrays optimization pixels conways-game-of-life

好的,所以我有一段真正需要优化的Python代码。

  • 这是一个小型(80x60像素)图像的生命游戏迭代,并从中提取RGB值。
  • 目前正在使用嵌套的for循环;我宁愿用更快的map() c函数替换那些for循环,但如果我这样做,我无法弄清楚如何获取x,y值,以及从范围外定义的本地值我需要定义的功能。
  • 使用map()会比当前的for循环更快吗?我怎么能使用它仍然得到x,y?
  • 我目前使用pygame Surfaces,我尝试了surfarray/pixelarray模块,但由于我正在更改/获取每个像素,因此它比Surface.get_at()/set_at()慢很多。
  • 另外,稍微不相干......你认为如果Python没有遍历数字列表而只是递增一个数字,就像在其他语言中那样可以更快地做到这一点吗?为什么python不包含普通的for()以及它们的foreach()?
  • 那里的条件数量可能会让事情变慢,对吧?最慢的部分是检查邻居(它在哪里构建列表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))

3 个答案:

答案 0 :(得分:3)

使你的代码变慢的原因可能不是循环,它们非常快。

您的代码执行速度慢的是函数调用次数。例如

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_atset_at调用都会锁定曲面,因此使用可用方法直接访问像素会更快。最合理的是Surface.get_buffer

使用map在您的示例中不起作用,因为您需要索引。只需80和60个数字,使用range()代替xrange()甚至可能更快。

答案 1 :(得分:2)

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代替{{ 1}}。不过,我会说它不太可能对性能产生重大影响。

编辑请注意,gs肯定是关于xrange循环不是代码中需要优化的主要内容...减少对{{1的多余调用更重要的是。事实上,我不确定用for替换循环是否真的会改善这里的性能......话虽如此,我发现get_at版本更具可读性(也许是因为我的FP背景...),所以你还是去吧。 ; - )

答案 2 :(得分:1)

由于您正在阅读并重写每个像素,我认为您可以通过不使用Surface来获得最佳的速度提升。

我建议首先拍摄80x60图像并将其转换为具有32位像素的普通位图文件。然后将像素数据读入python array对象。现在,您可以遍历array对象,读取值,计算新值,并以最大速度将新值戳到位。完成后,保存新的位图图像,然后将其转换为Surface

您也可以使用24位像素,但这应该更慢。 32位像素表示一个像素是一个32位整数值,这使得像素阵列更容易索引。 24位打包像素意味着每个像素是3个字节,这对索引来说更加烦人。

我相信通过这种方法你会比试图避免使用for获得更多的速度。如果你试试这个,请在​​这里发布一些东西,让我们知道它有多好用或者有用。祝你好运。

编辑:我认为array只有一个索引。我不确定你是如何设法让两个索引工作的。我期待你做这样的事情:

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只能包含一个类型,因此您需要将类型设置为“unsigned byte”,然后像我显示的那样进行索引。

当然__a是实际的array变量。

如果这些都没有用,请尝试将位图转换为列表,或者可能是三个列表。您可以使用嵌套列表来获取2D寻址。

我希望这会有所帮助。如果它没有帮助,那么我不理解你在做什么;如果你解释得更多,我会尝试改进答案。