Оптимизация итерации "Игры в жизнь" с использованием массива пикселей 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 не включает обычный 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 ...), так что в любом случае начинайте.;-)