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

如何优化Python中计算距离的checkRadars函数以提升性能?

问题描述

我用Python和Pygame开发了一款测试神经网络(主要针对NEAT算法)的简易赛车游戏,玩家需操控车辆沿赛道行驶且不能触碰边界。为让神经网络“感知”环境,每辆车都配备了多个传感器,用于计算不同角度下到赛道边界(由mask定义)的距离。少量车辆时运行正常,但当赛道上有20辆及以上车辆时,运行速度大幅下降。通过cProfile分析发现,checkRadars()函数是性能瓶颈——每帧更新时每辆车都会调用该函数。我已将部分计算移至循环外进行优化,但性能仍未达标,且length参数无法进一步降低(当前值已偏低),寻求进一步优化该函数的方法。

cProfile分析输出

38382471 function calls in 52.762 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001   52.762   52.762 <string>:1(<module>)
    26435    0.158    0.000    1.086    0.000 cargame.py:113(draw)
    26435    0.045    0.000    0.045    0.000 cargame.py:121(drawRadars)
    26435   14.103    0.001   22.550    0.001 cargame.py:128(checkRadars)
    26435    0.129    0.000   24.262    0.001 cargame.py:149(update)
    26435    0.060    0.000    0.282    0.000 cargame.py:157(checkColliding)
      273    0.001    0.000    0.011    0.000 cargame.py:169(drawOnMouse)

待优化的checkRadars函数代码

def checkRadars(self, mask):
    xcenter, ycenter = self.rect.center
    #self.radars.clear()
    v = (180-180*self.blindspot)/(self.radarCount-1)
    w = (90-90*self.blindspot)
    # dont mind these weird calculations
    for n in range(self.radarCount):
        length = 1
        x, y = xcenter, ycenter

        rangle = v*n-w
        rad = -math.radians(self.angle+rangle)


        while isInBound(x, y) and not mask.get_at((x, y)) and length < 100:
            length += 1
            x = int(xcenter + math.sin(rad) * length)
            y = int(ycenter - math.cos(rad) * length)
        if length == 100:
            length = length*10
优化方案

1. 预计算三角函数值

当前while循环中反复计算math.sin(rad)和math.cos(rad),但每个雷达角度的这两个值是固定的,可提前计算并复用:

def checkRadars(self, mask):
    xcenter, ycenter = self.rect.center
    v = (180-180*self.blindspot)/(self.radarCount-1)
    w = (90-90*self.blindspot)
    for n in range(self.radarCount):
        length = 1
        rangle = v*n-w
        rad = -math.radians(self.angle+rangle)
        # 预计算三角函数值
        sin_rad = math.sin(rad)
        cos_rad = math.cos(rad)
        x, y = xcenter, ycenter

        while isInBound(x, y) and not mask.get_at((x, y)) and length < 100:
            length += 1
            x = int(xcenter + sin_rad * length)
            y = int(ycenter - cos_rad * length)
        if length == 100:
            length = length*10

2. 替换mask.get_at()为高效线段碰撞检测

mask.get_at()是单像素级检测,每次调用开销极大。改用Pygame的mask线段碰撞检测,直接获取雷达线段与边界的交点,避免逐像素遍历:

def checkRadars(self, mask):
    xcenter, ycenter = self.rect.center
    v = (180-180*self.blindspot)/(self.radarCount-1)
    w = (90-90*self.blindspot)
    max_length = 100
    for n in range(self.radarCount):
        rangle = v*n-w
        rad = -math.radians(self.angle+rangle)
        # 计算雷达线段的终点坐标
        end_x = int(xcenter + math.sin(rad) * max_length)
        end_y = int(ycenter - math.cos(rad) * max_length)
        # 创建线段的临时mask并检测碰撞
        line_surf = pygame.Surface((abs(end_x-xcenter)+1, abs(end_y-ycenter)+1))
        pygame.draw.line(line_surf, (255,255,255), (0,0), (end_x-xcenter, end_y-ycenter))
        line_mask = pygame.mask.from_surface(line_surf)
        # 获取碰撞点偏移量
        collision_offset = mask.overlap(line_mask, (xcenter, ycenter))
        if collision_offset:
            # 计算实际距离
            length = math.hypot(collision_offset[0], collision_offset[1])
        else:
            length = max_length * 10
        # 后续处理length...

注:可预创建常用角度的线段mask,减少重复创建Surface的开销。

3. 增量式坐标计算,减少乘法操作

将当前的xcenter + sin_rad * length改为增量累加,每次循环仅做加法,降低计算开销:

def checkRadars(self, mask):
    xcenter, ycenter = self.rect.center
    v = (180-180*self.blindspot)/(self.radarCount-1)
    w = (90-90*self.blindspot)
    for n in range(self.radarCount):
        length = 1
        rangle = v*n-w
        rad = -math.radians(self.angle+rangle)
        sin_rad = math.sin(rad)
        cos_rad = math.cos(rad)
        # 计算每步的坐标增量
        dx = sin_rad
        dy = -cos_rad
        x, y = xcenter, ycenter

        while isInBound(x, y) and not mask.get_at((int(x), int(y))) and length < 100:
            length += 1
            x += dx
            y += dy
        if length == 100:
            length = length*10

若isInBound支持浮点坐标,可避免每次转换整数,进一步提升速度。

4. 减少isInBound()调用次数

若赛道mask已包含边界信息(即边界外的区域不在mask内),可直接移除isInBound()判断;若必须保留,可将其移至循环前,先判断雷达终点是否在边界内,再决定是否进入循环,避免每次循环都调用。

5. 批量处理多车辆雷达检测

将所有车辆的中心坐标、角度等数据存入numpy数组,利用向量化计算批量生成所有雷达线段,再一次性与mask做碰撞检测,利用CPU并行能力减少循环开销。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 07:15:33