Оптимизация итерации Game-of-Life с массивом пикселей RGB 80x60 - PullRequest
1 голос
/ 09 января 2010

Хорошо, у меня есть фрагмент кода Python, который действительно нуждается в оптимизации.

  • Это итерация Game-of-Life для небольшого (80x60 пикселей) изображения, извлекающая из него значения RGB.
  • в настоящее время использует вложенные циклы for; Я бы лучше поменял эти циклы for на более быструю функцию map() c, но если я это сделаю, я не смогу понять, как получить значения x, y или локальные значения, определенные вне области действия функции, которые мне нужно определить.
  • будет ли 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))

Ответы [ 3 ]

3 голосов
/ 09 января 2010

Что делает ваш код медленным, это, вероятно, не циклы, они невероятно быстрые.

Что замедляет ваш код, так это количество вызовов функций. Например

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

2 голосов
/ 09 января 2010
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. Я бы сказал, что это вряд ли существенно повлияет на производительность.

Редактировать: Обратите внимание, что gs, безусловно, прав в том, что циклы for не являются основным требованием оптимизации в вашем коде ... Сокращение лишних вызовов к get_at важнее , На самом деле, я не уверен, что замена циклов на map действительно улучшит производительность здесь вообще ... Сказав это, я нахожу версию map более читабельной (возможно, из-за моего фона FP ...) так что вот и все. ; -)

1 голос
/ 09 января 2010

Поскольку вы читаете и переписываете каждый пиксель, я думаю, что вы можете получить лучшее улучшение скорости, не используя 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-адресации.

Надеюсь, это поможет. Если это не помогает, тогда я не понимаю, что вы делаете; если вы объясните больше, я постараюсь улучшить ответ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...