Оптимизация итерации "Игры в жизнь" с использованием массива пикселей 80x60 RGB

StackOverflow https://stackoverflow.com/questions/2031216

Вопрос

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

  • Это итерация "Игры в жизнь" над небольшим (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)...Я заменил весь этот бит доступом к фрагменту в 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))
Это было полезно?

Решение

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

Поскольку Питон array может содержать только один тип, вы захотите установить для этого типа значение "байт без знака", а затем проиндексировать, как я показал.

Где , конечно __a является фактическим array переменная.

Если ничего из этого не помогает, попробуйте преобразовать свое растровое изображение в список или, возможно, в три списка.Вы можете использовать вложенные списки для получения 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.Однако я бы сказал, что это вряд ли окажет существенное влияние на производительность.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top