1

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

  • 这是对小(80x60 像素)图像的生命游戏迭代,并从中提取 RGB 值。
  • 当前使用嵌套的 for 循环;我宁愿将那些 for 循环换成更快的map()c 函数,但是如果这样做,我将无法弄清楚如何获取 x,y 值,也无法确定在函数范围之外定义的局部值d 需要定义。
  • 使用map()会比当前的 for 循环集更快吗?我怎么能使用它并且仍然得到x,y?
  • 我目前使用 pygame Surfaces,并且我已经尝试过这些surfarray/pixelarray模块,但是由于我正在更改/获取每个像素,所以它比Surface.get_at()/set_at().
  • 另外,有点无关紧要......如果Python不遍历数字列表而只是增加一个数字,你认为这可以更快吗,就像在其他语言中一样?为什么 python 不包含普通的 for() 以及它们的 foreach()?
  • 那里的条件数量可能也会让事情变慢,对吧?最慢的部分是检查邻居(它在哪里构建列表n)......我用二维数组上的切片访问替换了整个位,但它不能正常工作。

代码的编辑版本:

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))
4

3 回答 3

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))

Every get_at, set_atcall 会锁定表面,因此使用可用方法直接访问像素会更快。似乎最合理的一个是Surface.get_buffer

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

于 2010-01-08T22:37:27.817 回答
2
map(do_stuff, ((x, y) for x in xrange(80) for y in xrange(60)))

wheredo_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(replace ((x, y) ...)with [(x, y) ...]) 的第二个参数并使用range而不是xrange. 不过,我想说它不太可能对性能产生重大影响。

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

于 2010-01-08T23:00:48.873 回答
1

由于您正在读取和重写每个像素,因此我认为您可以通过不使用Surface.

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

您也可以使用 24 位像素,但这应该会更慢。32 位像素意味着一个像素是一个 32 位整数值,这使得像素数组更容易索引。24 位压缩像素意味着每个像素是 3 个字节,索引起来更烦人。

我相信与避免使用for. 如果您尝试这样做,请在此处发布一些内容,让我们知道它的效果如何。祝你好运。

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

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)

由于 Pythonarray只能保存一种类型,因此您需要将类型设置为“无符号字节”,然后像我展示的那样进行索引。

当然__a,实际array变量在哪里。

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

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

于 2010-01-08T23:52:30.270 回答