10 KiB
Day 20 - 性能瓶颈深度分析与优化
日期: 2025-12-24
主题: 解决导盲系统开启后卡顿问题
问题诊断
症状
- 导盲系统一开启就卡顿、不流畅、有延迟
- GPU 利用率仅 7%
- 帧处理 FPS 仅 3-4
确认的瓶颈(经过再三分析)
| 瓶颈 | 影响 | 状态 |
|---|---|---|
| 过量日志输出 | _detect_obstacles 每次调用输出 20+ 行 logger.info |
✅ 已修复 |
| GPU Semaphore 限流 | 默认只允许 2 个并发 GPU 调用 | ✅ 已修复 |
| 检测串行执行 | 盲道和障碍物检测顺序执行 | ✅ 已修复 |
实施的优化
1. 精简日志输出 (workflow_blindpath.py)
修改前:每次障碍物检测输出 20+ 行日志
logger.info(f"[_detect_obstacles] 开始执行...")
logger.info(f"[_detect_obstacles] 调用...")
for obj in detected_obstacles:
logger.info(f"物体 {i+1}...")
logger.info(f" - 类别: ...")
logger.info(f" - 面积: ...")
# 每个障碍物 5-6 行!
修改后:只输出一行摘要(每 30 帧)
if detected_obstacles and self.frame_counter % 30 == 0:
names = [o.get('name', '?') for o in detected_obstacles[:3]]
logger.info(f"[障碍物] 检测到 {len(detected_obstacles)} 个: {names}")
2. 增加 GPU 并发槽位 (obstacle_detector_client.py)
# 修改前
GPU_SLOTS = int(os.getenv("AIGLASS_GPU_SLOTS", "2"))
# 修改后
GPU_SLOTS = int(os.getenv("AIGLASS_GPU_SLOTS", "4"))
3. GPU 并行检测 (gpu_parallel.py)
使用 CUDA Stream 让盲道检测和障碍物检测并行执行。
4. TTS WebSocket 断连修复 (app_main.py)
问题:TTS 语音有时不播放,日志显示 [TTS->WS] Buffering TTS audio
原因:set_tts_websocket(ws) 只在 start_ai_with_text() 时调用,WebSocket 重连后引用丢失
修复:在 ws_audio 连接建立时立即保存引用
@app.websocket("/ws_audio")
async def ws_audio(ws: WebSocket):
global esp32_audio_ws
esp32_audio_ws = ws
from audio_stream import set_tts_websocket
set_tts_websocket(ws) # Day 20: 连接时立即保存
await ws.accept()
修改文件
| 文件 | 修改 |
|---|---|
workflow_blindpath.py |
精简日志输出 |
obstacle_detector_client.py |
GPU 槽位 2→4 |
gpu_parallel.py |
新建,CUDA Stream 并行 |
app_main.py |
TTS 修复、性能诊断 |
预期效果
- 日志 I/O 减少 95%:每次检测从 20 行减到 1 行
- GPU 并发能力翻倍:4 槽位 vs 2 槽位
- 检测延迟减半:并行执行 vs 串行执行
可视化网页优化
UI 改进
| 优化项 | 说明 |
|---|---|
| IMU 浮窗 | 宽度 600px,添加可折叠功能 |
| 折叠状态优化 | 折叠后只显示标题和按钮,完全隐藏 3D 模型和数据面板 |
| Badge 动画 | 连接中闪烁 (blink)、已连接脉冲 (pulse) |
| 按钮美化 | 渐变背景 + 悬停上浮效果 |
| 移动端适配 | @media 600px/1100px 响应式布局 |
| 状态中文化 | 📷 已连接 替代 Camera: connected |
| 底部边界修复 | 移除 .chat { height: 100vh } 解决超出问题 |
| 背景色统一 | .stage 使用 var(--card) 与右侧一致 |
| Favicon 添加 | 添加 /static/favicon.png |
折叠功能实现
/* 折叠状态 - 只显示标题和按钮 */
.imu-float.collapsed {
width: 180px;
height: 40px;
overflow: hidden;
}
.imu-float.collapsed .imu-row,
.imu-float.collapsed #imu_top_status {
display: none !important;
}
// JS 折叠逻辑
document.addEventListener('DOMContentLoaded', () => {
const imuFloat = document.getElementById('imuFloat');
const imuToggle = document.getElementById('imuToggle');
imuToggle.onclick = function(e) {
const isCollapsed = imuFloat.classList.toggle('collapsed');
this.textContent = isCollapsed ? '+' : '−';
};
});
修改文件
| 文件 | 修改 |
|---|---|
templates/index.html |
样式优化、折叠按钮、移动端适配、底部边界修复、Favicon |
static/main.js |
setBadge 支持 connecting、折叠逻辑、数据面板优化 |
static/favicon.png |
新增网站图标 |
待验证
部署后观察:
[PERF] 帧:60 | 客户端FPS:?? | 帧间隔:??ms | 广播:??ms | 导航:??ms
目标:导航 耗时 < 80ms
Day 20 追加修复(2025-12-24 下午)
问题复现
上午优化后,导航仍有严重卡顿。日志分析发现:
| 帧数 | 导航耗时 |
|---|---|
| 120 | 245.7ms |
| 240 | 1410.1ms |
| 420 | 1571.5ms |
| 660 | 1766.0ms |
根因: CUDA Stream 并行检测未生效,两个模型仍串行执行。
追加修复
| 文件 | 修改 |
|---|---|
workflow_blindpath.py |
UNIFIED_DETECTION_INTERVAL 10→20帧 |
workflow_blindpath.py |
OBSTACLE_CACHE_DURATION_FRAMES 12→20帧 |
gpu_parallel.py |
移除无效 CUDA Stream,改用 ThreadPoolExecutor 真正并行 |
预期效果
- 检测频率减半 → GPU 负载降低
- 线程池并行 → 盲道和障碍物检测真正同时执行
- 导航耗时目标:稳定在 200ms 以下
Day 20 可视化性能优化(下午)
问题复现
追加修复后,导航耗时仍为 267-501ms。服务器资源分析发现:
| 指标 | 值 | 含义 |
|---|---|---|
| app_main.py CPU | 120% | 超过1核,但受 Python GIL 限制 |
| GPU 使用率 | 8% | GPU 等待 CPU,利用率低 |
| 可视化耗时 | 200-300ms | 主要瓶颈! |
根因: _draw_visualizations 中的 numpy 逐像素半透明混合非常耗 CPU。
优化内容
| 文件 | 修改 |
|---|---|
workflow_blindpath.py |
mask 半透明填充 → 轮廓绘制 |
workflow_blindpath.py |
移除 image.copy() 避免复制开销 |
代码对比
# 修改前(慢 ~200-300ms)
- for c in range(3):
- local_region[:, :, c] = np.where(
- binary_mask > 0,
- (1 - alpha) * local_region[:, :, c] + alpha * color_overlay[:, :, c],
- local_region[:, :, c]
- )
# 修改后(快 ~5-10ms)
+ cv2.polylines(image, [points], isClosed=True, color=color, thickness=thickness)
预期效果
- 可视化耗时:200-300ms → ~10-20ms
- 导航总耗时目标:~100-150ms
Day 20 Numba 多核加速
问题背景
Python GIL 导致无法利用多核 CPU。即使使用 ThreadPoolExecutor,CPU 密集型的 numpy 操作也只能使用单核。
解决方案
引入 Numba JIT 编译,将 Python 代码编译为机器码并启用多核并行:
from numba import jit, prange
@jit(nopython=True, parallel=True, cache=True)
def compute_mask_stats_numba(mask: np.ndarray) -> tuple:
for i in prange(h): # 自动多核并行
for j in range(w):
if mask[i, j] > 0:
...
新增文件
| 文件 | 说明 |
|---|---|
numba_utils.py |
Numba 加速工具函数:mask 像素计数、统计、交集计算 |
修改内容
| 文件 | 修改 |
|---|---|
obstacle_detector_client.py |
使用 Numba 加速 mask 统计和交集计算 |
app_main.py |
启动时预热 Numba JIT 编译 |
预期效果
- 多核利用率提升:从单核 ~120% 分摊到多核
- mask 操作加速:numpy 操作 → Numba 编译后 10-100x 提升
- 首次调用无延迟:启动时预热 JIT 编译
Day 20 TensorRT 加速集成 (17:30)
问题背景
为了进一步加速 GPU 推理,将 YOLO 模型导出为 TensorRT 引擎。
实施内容
| 步骤 | 说明 |
|---|---|
| 模型导出 | yolo-seg.engine、yoloe-11l-seg.engine、trafficlight.engine (FP16) |
| 工具函数 | model_utils.py - get_best_model_path() 自动选择 .engine |
| 代码适配 | 全部模型加载点改用工具函数 |
遇到的问题与修复
问题1:.to() 和 .fuse() 不兼容 TensorRT
TypeError: model='model/yolo-seg.engine' should be a *.pt PyTorch model
原因:TensorRT 引擎已经在 GPU 上,不能调用 .to("cuda") 和 .fuse()
修复:添加 is_tensorrt_engine() 检测函数,条件跳过
| 文件 | 修改 |
|---|---|
model_utils.py |
新增 is_tensorrt_engine() 函数 |
app_main.py |
跳过 .to() 和 .fuse() |
models.py |
跳过 .to() 和 .fuse() |
yoloe_backend.py |
跳过 .to() 和 get_text_pe() |
obstacle_detector_client.py |
跳过 .to() 和 .fuse() |
workflow_crossstreet.py |
跳过 .to() |
问题2:StopIteration 错误
print(f"[NAVIGATION] 模型设备: {next(obstacle_detector.model.parameters()).device}")
StopIteration
原因:TensorRT 引擎没有 .parameters() 迭代器
修复:检测 TensorRT 模式时跳过设备打印
问题3:推理尺寸不匹配
[GPU_PARALLEL] 盲道检测失败: input size torch.Size([1, 3, 640, 640]) not equal to max model size (1, 3, 480, 480)
原因:TensorRT 引擎用 480x480 导出,但代码硬编码 imgsz=640
修复:统一使用环境变量 AIGLASS_YOLO_IMGSZ=480
| 文件 | 修改 |
|---|---|
yoloe_backend.py |
默认 imgsz 改为从环境变量读取 |
yolomedia.py |
移除 4 处硬编码 imgsz=640 |
修复后状态
服务启动成功,TensorRT 引擎正常加载:
[NAVIGATION] TensorRT 引擎已加载,跳过 .to() 和 .fuse()
[TRT] Loaded engine size: 140 MiB
⚠️ 待验证问题 (Day 21 继续)
1. 语音识别停止响应 🔴
症状:停止盲道导航后,语音指令不再被识别
日志分析:
- 音频仍在接收:
[AUDIO] 📥 Received: 8900 packets... - 但没有 ASR 输出
可能原因:
- ASR 状态逻辑问题
- 需要检查
state=CHAT时 ASR 是否正常处理
2. TensorRT 预热警告(非致命)
[NAVIGATION] 模型预热失败: input size torch.Size([1, 3, 640, 640]) not equal to max model size (1, 3, 480, 480)
预热代码尝试用 640x640,但引擎用 480 导出。实际推理正常,仅预热失败。
3. 画面延迟约 2 秒
导航启动后画面流畅但有约 2 秒延迟,需进一步分析是网络还是处理延迟。