# Day 20 - 性能瓶颈深度分析与优化 **日期**: 2025-12-24 **主题**: 解决导盲系统开启后卡顿问题 --- ## 问题诊断 ### 症状 - 导盲系统一开启就卡顿、不流畅、有延迟 - GPU 利用率仅 7% - 帧处理 FPS 仅 3-4 ### 确认的瓶颈(经过再三分析) | 瓶颈 | 影响 | 状态 | |------|------|------| | **过量日志输出** | `_detect_obstacles` 每次调用输出 20+ 行 logger.info | ✅ 已修复 | | **GPU Semaphore 限流** | 默认只允许 2 个并发 GPU 调用 | ✅ 已修复 | | **检测串行执行** | 盲道和障碍物检测顺序执行 | ✅ 已修复 | --- ## 实施的优化 ### 1. 精简日志输出 (`workflow_blindpath.py`) **修改前**:每次障碍物检测输出 20+ 行日志 ```python 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 帧) ```python 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`) ```python # 修改前 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` 连接建立时立即保存引用 ```python @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` | ### 折叠功能实现 ```css /* 折叠状态 - 只显示标题和按钮 */ .imu-float.collapsed { width: 180px; height: 40px; overflow: hidden; } .imu-float.collapsed .imu-row, .imu-float.collapsed #imu_top_status { display: none !important; } ``` ```javascript // 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()` 避免复制开销 | ### 代码对比 ```diff # 修改前(慢 ~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 代码编译为机器码并启用多核并行: ```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 秒延迟,需进一步分析是网络还是处理延迟。