如何在Pygame中使用多进程加速百万矩形绘制?
优化百万矩形绘制速度的方案
核心限制说明
PyGame底层依赖的SDL渲染上下文是单线程安全的,不能直接在多线程中调用绘制API操作主屏幕Surface,否则会导致渲染错乱或程序崩溃。因此不能直接照搬OpenMP的并行绘制思路,需要换用「并行预处理离屏渲染+主屏幕合并」的方案。
1. 基础优化:减少冗余开销与启用硬件加速
先从代码结构和渲染模式入手,砍掉不必要的对象封装开销,同时开启PyGame硬件加速:
import pygame from random import randint pygame.init() # 开启硬件加速与双缓冲,提升渲染效率 screen = pygame.display.set_mode((640, 480), pygame.HWSURFACE | pygame.DOUBLEBUF) screen.fill((0,0,0)) # 直接存储矩形参数元组,避免类属性访问开销 rects_data = [] for _ in range(1000000): color = (randint(0,255), randint(0,255), randint(0,255)) pos = (randint(0, 639), randint(0, 479)) width = 639 - randint(pos[0], 639) height = 479 - randint(pos[1], 479) rects_data.append((color, pos, (width, height))) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() screen.fill((0,0,0)) # 直接遍历参数绘制,减少对象调用开销 for color, pos, size in rects_data: pygame.draw.rect(screen, color, pygame.Rect(pos, size)) pygame.display.flip()
2. 并行分块渲染:利用多线程预处理离屏Surface
将屏幕划分为多个区域,每个区域在单独线程中渲染到离屏Surface,最后合并到主屏幕,充分利用多CPU核心:
import pygame from random import randint from concurrent.futures import ThreadPoolExecutor pygame.init() screen_width, screen_height = 640, 480 screen = pygame.display.set_mode((screen_width, screen_height), pygame.HWSURFACE | pygame.DOUBLEBUF) screen.fill((0,0,0)) # 预处理所有矩形参数 rects_data = [] for _ in range(1000000): color = (randint(0,255), randint(0,255), randint(0,255)) pos = (randint(0, screen_width-1), randint(0, screen_height-1)) width = screen_width - 1 - randint(pos[0], screen_width-1) height = screen_height - 1 - randint(pos[1], screen_height-1) rects_data.append((color, pos, (width, height))) # 单块渲染函数:只处理指定区域内的矩形 def render_block(block_rect): block_surf = pygame.Surface((block_rect.width, block_rect.height)) block_surf.fill((0,0,0)) # 筛选当前块内的矩形并绘制 for color, pos, size in rects_data: rect = pygame.Rect(pos, size) if rect.colliderect(block_rect): # 转换为块Surface的局部坐标 local_pos = (pos[0] - block_rect.x, pos[1] - block_rect.y) pygame.draw.rect(block_surf, color, pygame.Rect(local_pos, size)) return block_rect, block_surf # 按CPU核心数划分屏幕块(示例为4块) num_blocks = 4 block_width = screen_width // num_blocks blocks = [pygame.Rect(i*block_width, 0, block_width, screen_height) for i in range(num_blocks)] # 创建线程池 executor = ThreadPoolExecutor(max_workers=num_blocks) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: executor.shutdown() pygame.quit() exit() # 并行渲染所有块 futures = [executor.submit(render_block, block) for block in blocks] # 将渲染好的块合并到主屏幕 for future in futures: block_rect, block_surf = future.result() screen.blit(block_surf, block_rect.topleft) pygame.display.flip()
3. 极致优化:直接操作像素数组
跳过PyGame的draw.rectAPI开销,用pygame.surfarray直接操作屏幕像素缓冲区,速度提升最明显:
import pygame from random import randint import numpy as np pygame.init() screen_width, screen_height = 640, 480 screen = pygame.display.set_mode((screen_width, screen_height), pygame.HWSURFACE | pygame.DOUBLEBUF) # 获取屏幕的numpy像素数组,直接操作像素 screen_array = pygame.surfarray.pixels3d(screen) # 预处理矩形的坐标范围与颜色 rects_data = [] for _ in range(1000000): color = (randint(0,255), randint(0,255), randint(0,255)) x1, y1 = randint(0, screen_width-1), randint(0, screen_height-1) x2 = screen_width - 1 - randint(x1, screen_width-1) y2 = screen_height - 1 - randint(y1, screen_height-1) rects_data.append((x1, y1, x2, y2, color)) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() # 清空屏幕像素 screen_array[:] = 0 # 直接给矩形区域填充颜色 for x1, y1, x2, y2, (r,g,b) in rects_data: # 确保坐标不超出屏幕范围 x1_clamp = max(0, x1) x2_clamp = min(screen_width, x2) y1_clamp = max(0, y1) y2_clamp = min(screen_height, y2) if x1_clamp >= x2_clamp or y1_clamp >= y2_clamp: continue # 批量设置像素颜色 screen_array[y1_clamp:y2_clamp, x1_clamp:x2_clamp] = (r, g, b) pygame.display.flip()
内容的提问来源于stack exchange,提问作者Eyman Farooq
相关产品推荐
相关产品推荐

