在 python、go 或 julia 中快速直接访问像素

问题描述 投票:0回答:1

我编写了一个小程序,可以创建随机噪声并将其全屏显示(5K 分辨率)。我用 pygame 来实现。但刷新速度却非常慢。 surfarray.blit_array 和随机生成都需要花费大量时间。有什么办法可以加快这个速度吗?我也可以灵活地使用 julia 或 golang 来代替。或者还有psychopy或octave与psychotoolbox(但是这些似乎不能在linux/wayland下工作)。

这是我写的:


import pygame
import numpy as N
import pygame.surfarray as surfarray
from numpy import int32, uint8, uint

 
def main():
     
    pygame.init()
     
    #flags = pygame.OPENGL | pygame.FULLSCREEN   # OpenGL does not want to work with surfarray
    flags = pygame.FULLSCREEN
    screen = pygame.display.set_mode((0,0), flags=flags, vsync=1)
    w, h = screen.get_width(), screen.get_height()

    clock = pygame.time.Clock()
    font = pygame.font.SysFont("Arial" , 18 , bold = True)
     
    # define a variable to control the main loop
    running = True

    def fps_counter():
        fps = str(int(clock.get_fps()))
        fps_t = font.render(fps , 1, pygame.Color("RED"))
        screen.blit(fps_t,(0,0))

                    
     
    # main loop
    while running:
        # event handling, gets all event from the event queue
        for event in pygame.event.get():
            # only do something if the event is of type QUIT
            if event.type == pygame.QUIT:
                # change the value to False, to exit the main loop
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    return
        array_img = N.random.randint(0, high=100, size=(w,h,3), dtype=uint)
        surfarray.blit_array(screen, array_img)
        fps_counter()
        pygame.display.flip()
        clock.tick()
        #print(clock.get_fps())
     
# run the main function only if this module is executed as the main script
# (if you import this as a module then nothing is executed)
if __name__=="__main__":
    # call the main function
    main()

我需要至少 30 fps 的刷新率才能发挥作用

python numpy pygame-surface psychopy psychtoolbox
1个回答
0
投票

更快的随机数生成

生成随机数的成本很高。当随机数生成器 (RNG) 需要统计上准确时(即随机数即使经过一些变换也需要看起来非常随机),并且数字是按顺序生成时,尤其如此。

事实上,对于密码学用途或某些数学(蒙特卡罗)模拟,目标 RNG 需要足够先进,以便几个后续生成的随机数之间不存在统计相关性。实际上,执行此操作的软件方法非常昂贵,以至于现代主流处理器提供了一种通过“特定指令”来执行此操作的方法。但并非所有处理器都支持这一点,并且 AFAIK Numpy 不使用它(当然是为了可移植性,因为在多台机器上具有相同种子的随机序列预计会给出相同的结果)。 幸运的是,RNG 在大多数其他用例中通常不需要那么准确。他们只需要看起来很随意。有

很多方法

可以做到这一点(例如 Mersenne Twister、Xoshiro、Xorshift、PCG/LCG)。 RNG 的性能、准确性和专业化之间经常需要进行权衡。由于 Numpy 需要提供相对准确的通用 RNG(尽管据我所知并不意味着用于加密用例),因此其性能次优也就不足为奇了。 这里提供了对许多不同方法的有趣回顾(尽管结果应该持保留态度,特别是在性能方面,因为 SIMD 友好等细节对于许多用例中的性能至关重要)。

在纯 Python(使用 CPython)中实现非常快速的随机数生成器是不可能的,但可以使用

Numba (或 Cython)来做到这一点。不过,可能有一些用本机语言编写的快速现有模块可以做到这一点。最重要的是,我们可以使用多个线程来加速操作。为了简单起见,我选择实现 Xorshift64 RNG(也因为它相对较快)。

import numba as nb @nb.njit('uint64(uint64,)') def xorshift64_step(seed): seed ^= seed << np.uint64(13) seed ^= seed >> np.uint64(7) seed ^= seed << np.uint64(17) return seed @nb.njit('uint64()') def init_xorshift64(): seed = np.uint64(np.random.randint(0x10000000, 0x7FFFFFFF)) # Bootstrap return xorshift64_step(seed) @nb.njit('(uint64, int_)') def random_pixel(seed, high): # Must be a constant for sake of performance and in the range [0;256] max_range = np.uint64(high) # Generate 3 group of 16 bits from the RNG bits1 = seed & np.uint64(0xFFFF) bits2 = (seed >> np.uint64(16)) & np.uint64(0xFFFF) bits3 = seed >> np.uint64(48) # Scale the numbers using a trick to avoid a modulo # (since modulo are inefficient and statistically incorrect here) r = np.uint8(np.uint64(bits1 * max_range) >> np.uint64(16)) g = np.uint8(np.uint64(bits2 * max_range) >> np.uint64(16)) b = np.uint8(np.uint64(bits3 * max_range) >> np.uint64(16)) new_seed = xorshift64_step(seed) return (r, g, b, new_seed) @nb.njit('(int_, int_, int_)', parallel=True) def pseudo_random_image(w, h, high): res = np.empty((w, h, 3), dtype=np.uint8) for i in nb.prange(w): # RNG seed initialization seed = init_xorshift64() for j in range(h): r, g, b, seed = random_pixel(seed, high) res[i, j, 0] = r res[i, j, 1] = g res[i, j, 2] = b return res

代码相当大,但在我的 6 核 i5-9600KF CPU 上,它比 Numpy 快约 22 倍。请注意,类似的代码可以在 Julia 中使用,以便获得快速实现(因为 Julia 使用类似于 Numba 的基于 LLVM 的 JIT)。

在我的机器上,这足以达到
75 FPS(最大)
,而初始代码达到 16 FPS。

更快的操作和渲染 在大多数平台上,生成新的随机数组

受到页面错误速度
的限制。这会显着减慢计算速度。在 Python 中缓解这种情况的唯一方法是创建一次 Brame 缓冲区并执行“in-place”操作。此外,PyGame 当然会在内部执行复制(也可能执行许多绘制调用),因此使用较低级别的 API 可以显着加快速度。尽管如此,该操作可能会受到内存限制,并且没有什么可以避免的。不过此时对您来说可能已经足够快了。

最重要的是,

帧在 GPU 上渲染,因此 CPU 需要发送/复制 GPU 上的缓冲区,通常通过独立 GPU 的 PCIe 互连。对于宽屏幕来说,这个操作不是很快。 实际上,您可以使用着色器

(或 OpenCL/CUDA 等工具)直接在 GPU 上生成随机图像。这可以避免上述开销,并且 GPU 可以比 CPU 更快地完成此任务。

© www.soinside.com 2019 - 2024. All rights reserved.