在 80x60 RGB 像素阵列上优化生命游戏迭代

发布于 2024-08-17 10:02:16 字数 3085 浏览 4 评论 0原文

好的,我有一段确实需要优化的 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))

Okay, so I've got a piece of Python code which really needs optimizing.

  • It's a Game-of-Life iteration over a small (80x60-pixel) image and extracts the RGB values from it.
  • currently using nested for-loops; I'd rather swap out those for loops for the faster map() c function, but if I do that I can't figure out how I can get the x,y values, nor the local values defined out of the scope of the functions I'd need to define.
  • would using map() be any faster than this current set of for loops? How could I use it and still get x,y?
  • I currently use pygame Surfaces, and I've tried the surfarray/pixelarray modules, but since I'm changing/getting every pixel, it's a lot slower than Surface.get_at()/set_at().
  • Also, slightly irrelevant... do you think this could be made quicker if Python wasn't traversing a list of numbers but just incrementing a number, like in other languages? Why doesn't python include a normal for() as well as their foreach()?
  • The amount of conditionals there probably makes things slower too, right? The slowest part is checking for neighbours (where it builds the list n)... I replaced that whole bit with slice access on a 2D array but it doesn't work properly.

Redacted version of code:

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

Full version of the function:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

痴情换悲伤 2024-08-24 10:02:17
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 背景......),所以无论如何,你就可以了。 ;-)

map(do_stuff, ((x, y) for x in xrange(80) for y in xrange(60)))

where do_stuff would presumably be defined like so:

def do_stuff(coords):
    r, g, b, a = get_at(coords)
    # ... whatever you need to do with those ...
    set_at(coords, (r, g, b))

You could alternatively use a list comprehension instead of a generator expression as the second argument to map (replace ((x, y) ...) with [(x, y) ...]) and use range instead of xrange. I'd say that it's not very likely to have a significant effect on performance, though.

Edit: Note that gs is certainly right about the for loops not being the main thing in need of optimisation in your code... Cutting down on superfluous calls to get_at is more important. In fact, I'm not sure if replacing the loops with map will actually improve performance here at all... Having said that, I find the map version more readable (perhaps because of my FP background...), so here you go anyway. ;-)

兮子 2024-08-24 10:02:17

由于您正在读取和重写每个像素,因此我认为不使用Surface可以获得最佳的速度提升。

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

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

我相信,与尝试避免使用 for 相比,通过这种方法您将获得更快的速度。如果您尝试此操作,请在此处发布一些内容,让我们知道它的效果如何。祝你好运。

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

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 只能保存单个类型,因此你需要将类型设置为“无符号字节”,然后像我所示的那样设置索引。

当然,__a 是实际的array 变量。

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

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

Since you are reading and rewriting every pixel, I think you can get the best speed improvement by not using a Surface.

I suggest first taking your 80x60 image and converting it to a plain bitmap file with 32-bit pixels. Then read the pixel data into a python array object. Now you can walk over the array object, reading values, calculating new values, and poking the new values into place with maximum speed. When done, save your new bitmap image, and then convert it to a Surface.

You could also use 24-bit pixels, but that should be slower. 32-bit pixels means one pixel is one 32-bit integer value, which makes the array of pixels much easier to index. 24-bit packed pixels means each pixel is 3 bytes, which is much more annoying to index into.

I believe you will gain much more speed out of this approach than by trying to avoid the use of for. If you try this, please post something here to let us know how well it worked or didn't. Good luck.

EDIT: I thought that an array has only a single index. I'm not sure how you managed to get two indexes to work. I was expecting you to do something like this:

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)

Since a Python array can only hold a single type, you will want to set the type to "unsigned byte" and then index like I showed.

Where of course __a is the actual array variable.

If none of this is helpful, try converting your bitmap into a list, or perhaps three lists. You can use nested lists to get 2D addressing.

I hope this helps. If it is not helpful, then I am not understanding what you are doing; if you explain more I'll try to improve the answer.

偷得浮生 2024-08-24 10:02:16

让你的代码变慢的可能不是循环,它们的速度非常快。

导致代码执行速度变慢的是函数调用的数量。例如

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() 甚至可能更快。

What's making your code slow is probably not the loops, they are incredibly fast.

What slows done your code are the number of function calls. For example

pixelR = get_at((x,y))[0]
pixelG = get_at((x,y))[1]
pixelB = get_at((x,y))[2]

is a lot slower than (about 3 times I guess)

r, g, b, a = get_at((x,y))

Every get_at, set_at call locks the surface, therefore it's faster to directly access the pixels using the available methods. The one that seems most reasonable is Surface.get_buffer.

Using map doesn't work in your example, because you need the indexes. With as few as 80 and 60 numbers it might even be faster to use range() instead of xrange().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文