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

y53ybaqx  于 2023-11-18  发布在  Python
关注(0)|答案(1)|浏览(102)

我写了一个小程序,创建随机噪音,并显示它全屏(5 K分辨率)。我使用pygame。但是刷新率非常慢。surfarray.blit_array和随机生成都需要很多时间。有什么方法可以加快速度吗?我也可以灵活地使用julia或golang来代替。或者也可以使用psychotoolbox的psychopy或octave(但是这些似乎在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的,它是有用的

xjreopfe

xjreopfe1#

更快的随机数生成

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

事实上,对于密码学用途或一些数学用途,(蒙特卡罗)模拟,目标RNG需要足够先进,以便在几个后续生成的随机数之间没有统计相关性。在实践中,软件方法是如此昂贵,以至于现代主流处理器提供了一种使用specific instructions的方法。但并非所有处理器都支持这一点,而AFAIK Numpy不使用它(当然是为了便于移植,因为在多台机器上具有相同种子的随机序列预计会得到给予相同的结果)。
幸运的是,在大多数其他用例中,RNG通常不需要如此精确,它们只需要看起来非常随机即可。(例如Mersenne Twister,Xoshiro,Xorshift,PCG/LCG)。在性能,由于Numpy需要提供一个相对准确的通用RNG,(尽管AFAIK不打算用于加密用例),但其性能次优也就不足为奇了。
here提供了对许多不同方法的有趣回顾(尽管结果应该持保留态度,特别是关于性能,因为在许多用例中,像SIMD友好这样的细节对性能至关重要)。
在纯Python中实现一个非常快的随机数生成器(使用CPython)是不可能的,但是可以使用Numba(或Cython)来实现。虽然可能有用本地语言编写的快速模块来实现这一点。最重要的是,我们可以使用多个线程来加速操作。为了简单起见,我选择实现Xorshift 64 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- 9600 KF CPU上,它比Numpy快22倍。请注意,可以在Julia中使用类似的代码,以便快速实现(因为Julia使用基于LLVM的JIT,类似于Numba)。
在我的机器上,这足以达到75 FPS(最大),而初始代码达到16 FPS。

更快的操作和渲染

在大多数平台上,生成新的随机数组会受到页面错误速度的限制,这会大大降低计算速度。在Python中,唯一的缓解方法是创建一次brame buffer并执行inplace操作。而且PyGame肯定会在内部进行复制(可能还有许多绘制调用),因此使用较低级别的API会快得多。这个操作很可能是内存受限的,没有什么可以避免的。不过在这一点上,它对你来说已经足够快了。
最重要的是,帧在GPU上渲染,因此CPU需要发送/复制GPU上的缓冲区,通常通过独立GPU的PCIe互连。此操作对于宽屏幕来说不是很快。
实际上,你可以直接在GPU上使用着色器**(或OpenCL/CUDA等工具)**生成随机图像。这可以避免上述开销,并且GPU可以比CPU更快地完成这一任务。

相关问题