You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt6实现Game of Life运行缓慢,求性能优化方案

生命游戏(Game of Life)性能优化方案

问题背景

我用PyQt6编写了生命游戏程序,但运行速度极慢,希望从多核利用、GPU加速、代码优化三个方向提升效率,相关代码如下:

main.py

from numpy import empty
from sys import argv
from cell import cell
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QBrush, QColor, QKeyEvent, QPainter, QPen, QPaintEvent
from PyQt6.QtWidgets import QApplication, QWidget

class Window(QWidget):
    def __init__(self, parent = None) -> None:
        super().__init__(parent)
        self.windowWidth = 1000
        self.windowHeight = 1000
        self.resolution = 2
        self.cells = empty((self.windowWidth // self.resolution, self.windowHeight // self.resolution), dtype=object)
        cellsShape = self.cells.shape
        for i in range(cellsShape[0]):
            for j in range(cellsShape[1]):
                self.cells[i][j] = cell(i, j, self.resolution)
        self.setWindowTitle("Game of Life")
        self.setGeometry((1920 - 800) // 2, (1080 - 800) // 2, 800, 800)
        self.setStyleSheet("background-color:rgb(20, 20, 20);")
        self.graphicTimer = QTimer(self)
        self.baseTimer = QTimer(self)
        self.graphicTimer.timeout.connect(self.update)
        self.graphicTimer.start(30)
        self.show()

    def run(self) -> None:
        for rows in self.cells:
            for cell in rows:
                cell.calculateNewState(self.cells)
        for rows in self.cells:
            for cell in rows:
                cell.setNewState()

    def keyPressEvent(self, event: QKeyEvent) -> None:
        key = event.key()
        if key == Qt.Key.Key_S:
            self.closeWindow()
        event.accept()

    def paintEvent(self, event: QPaintEvent) -> None:
        self.run()
        painter = QPainter()
        painter.begin(self)
        painter.setPen(QPen(QColor(20, 20, 20),  -1, Qt.PenStyle.SolidLine))
        painter.setBrush(QBrush(QColor(255, 255, 255), Qt.BrushStyle.SolidPattern))
        for rows in self.cells:
            for cell in rows:
                if cell.state == True:
                    painter.drawRect(cell.posX * cell.cellWidth, cell.posY * cell.cellWidth, cell.cellWidth, cell.cellWidth)
        painter.end()
        event.accept()

    def closeWindow(self) -> None:
        print("closing window ...")
        self.close()


if __name__ == "__main__":
    App = QApplication(argv)
    window = Window()
    exit(App.exec())

cell.py

from numpy.random import choice
from PyQt6.QtWidgets import QWidget


class cell(QWidget):
    def __init__(self, x, y, width, state = None, parent = None) -> None:
        super().__init__(parent)
        self.posX = x
        self.posY = y
        self.cellWidth = width
        self.state = state
        if self.state == None:
            self.state = choice([True, False])
        self.newState = None
        self.aliveNeighbors = 0
    
    def countAliveNeighbors(self, cells) -> None:
        cellsShape = cells.shape
        self.aliveNeighbors = 0
        for i in range(-1, 2):
            for j in range(-1, 2):
                neighbor = cells[(self.posX + i + cellsShape[0]) % cellsShape[0]][(self.posY + j + cellsShape[1]) % cellsShape[1]]
                if neighbor.state == True:
                    self.aliveNeighbors += 1
        if self.state == True:
            self.aliveNeighbors -= 1

    def calculateNewState(self, cells) -> None:
        self.countAliveNeighbors(cells)
        if self.state == False and self.aliveNeighbors == 3:
            self.newState = True
        elif self.state == True and (self.aliveNeighbors > 3 or self.aliveNeighbors < 2):
            self.newState = False
        else:
            self.newState = self.state
    
    def setNewState(self) -> None:
        self.state = self.newState
        self.newState = None

优化方案

1. 利用CPU多核能力

Python的GIL会限制单线程CPU利用率,CPU密集型任务需用多进程突破限制:

  • 拆分网格:将生命游戏的网格分成多个独立区块,每个进程负责一个区块的状态计算(需处理区块边缘细胞的跨区块邻居)。
  • 使用multiprocessing或concurrent.futures.ProcessPoolExecutor:用进程池分发计算任务,避免手动管理进程的开销。
  • 用共享内存存储状态:将细胞状态改为numpy布尔数组,通过multiprocessing.Array或numpy共享内存数组传递数据,减少进程间数据拷贝的开销。
  • 复用进程:提前启动进程池,避免频繁创建销毁进程的成本。

2. 借助GPU加速计算与显示

计算加速(核心逻辑)

生命游戏的细胞状态计算是高度并行的,适合GPU处理:

  • 用CUDA/OpenCL:如果是NVIDIA显卡,可使用numba.cuda将状态计算函数编译为CUDA核函数,在GPU上并行计算每个细胞的新状态;也可使用PyOpenCL实现跨平台GPU加速。
  • 用深度学习框架:利用PyTorch/TensorFlow的张量运算,将细胞状态转为GPU张量,通过卷积操作计算邻居数量,再批量更新状态,框架会自动调度GPU资源。

显示加速

PyQt默认CPU渲染,可通过GPU加速绘制:

  • 切换到QOpenGLWidget:将绘制逻辑迁移到OpenGL环境,用GPU批量绘制存活细胞,比如将存活细胞的坐标转为顶点缓冲区,一次性提交给GPU渲染,替代逐个drawRect的低效操作。
  • 使用Qt Quick:若允许重构UI,Qt Quick(QML)默认利用GPU渲染,可大幅提升绘制性能,适合大量图形元素的场景。

3. 代码层面的性能优化

核心数据结构重构

  • 替换对象数组为numpy布尔数组:原代码用dtype=object的数组存储cell对象,每个对象的属性访问和方法调用开销极大。改为numpy.ndarray存储布尔值(True=存活,False=死亡),所有计算用向量化操作完成,速度可提升数倍甚至数十倍。
  • 示例代码:
    import numpy as np
    from scipy.ndimage import convolve
    
    # 初始化状态数组
    self.state = np.random.choice([True, False], size=(self.windowWidth//self.resolution, self.windowHeight//self.resolution), dtype=bool)
    # 3x3卷积核,排除自身
    kernel = np.ones((3,3), dtype=int)
    kernel[1,1] = 0
    
    def run(self):
        # 计算每个细胞的存活邻居数(循环边界用wrap模式)
        neighbor_count = convolve(self.state, kernel, mode='wrap')
        # 批量更新状态
        self.new_state = np.logical_or(
            np.logical_and(self.state == False, neighbor_count == 3),
            np.logical_and(self.state == True, np.logical_and(neighbor_count >=2, neighbor_count <=3))
        )
        self.state, self.new_state = self.new_state, self.state
    

分离计算与UI线程

  • 原代码在paintEvent中调用run(),会阻塞UI线程导致卡顿。将计算逻辑放到QThread中,线程定时计算新状态,然后通过信号通知UI线程更新,UI线程只负责绘制,避免计算和渲染互相阻塞。

优化绘制逻辑

  • 批量绘制:收集所有存活细胞的矩形区域,用QPainter.drawRects()一次性绘制,替代逐个drawRect的多次调用,减少绘制指令开销。
  • 预渲染:将状态数组转为QImage,直接绘制整个图像,比逐个绘制矩形更快。比如将存活细胞设为白色,死亡设为黑色,生成QImage后用painter.drawImage()绘制。

移除不必要的类继承

  • cell类继承QWidget完全多余,它不需要任何UI组件,改为普通数据类或直接用数组存储状态,消除QWidget带来的内存和性能开销。

减少循环嵌套

  • 用numpy向量化操作替代Python层面的嵌套循环,numpy的底层是C实现,比Python循环快得多。比如计算邻居数、更新状态都用数组操作,避免遍历每个细胞对象。

内容的提问来源于stack exchange,提问作者DSA5252

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 07:44:58