OpenClaw 4.29 性能优化实录:从 40 秒到底要多久?
最近用 OpenClaw 4.29 跑 agent,Gateway 模式下从发消息到收到回复总要 40 秒左右。作为一个对响应时间敏感的用户,我花了大半天跟踪这个问题,打了 6 轮 patch,最后发现——痛点不在我以为的地方。
一、问题拆解:prep 阶段在做什么?
OpenClaw 在每次 agent run 时有一个 prep 阶段,它在模型接收用户消息之前完成所有准备工作。通过 embedded trace 日志,我把这个阶段拆成了几个部分:
| 阶段 | 耗时 | 占比 |
|---|---|---|
| runtime-plugins | ~9.4s | 52% |
| core-plugin-tools | ~8.8s | 49% |
| system-prompt | ~3.7s | 20% |
| stream-setup | ~3.5s | 19% |
注:百分比之和超过 100%,因为这些阶段有重叠。但可以清晰看出:runtime-plugins 和 core-plugin-tools 是大头。
二、版本变化带来的挑战
这次跟踪是在 OpenClaw 4.29 上做的。这个版本有一个重大变化:
dist 目录结构从 chunks/ 子目录变成了扁平化的 hash-named ESM 模块。以前的 provider-runtime.js 现在叫 loader-CLyHx60E.js,且每次升级文件名 hash 都变。
这意味着:
- 以前基于固定路径的 patch 全部失效
- 每次升级都要重新定位目标函数在哪个 hash 文件里
- 维护成本极高
三、调查过程:从假设到真相
1. 假设:cache 不生效
我以为是旧版的 cache: true patch 被覆盖了。但查了源码后发现:4.29 的 cacheEnabled 默认就是 true,问题不在这。
2. 发现:9 次重复加载
通过日志追踪,发现单次 agent run 中 loadOpenClawPlugins 被调用了 9 次,每次都是 CACHE MISS。
原因:cache 是内存级的(pluginLoaderCacheState Map),每次 agent run 都是独立进程,进程结束后 cache 就清空了。跨进程不共享。
3. 尝试:减少 plugin 数量
我在 openclaw.json 里配置了 plugins.allow 白名单,将 118 个 plugin 过滤到 13 个。
结果:loading 时间仍然是 ~7 秒。
原因:瓶颈不在遍历数量,在单个 plugin 的 jiti 动态加载初始化开销。即使只加载 9 个 plugin,Gateway 启动时也需要 7-8 秒。
4. 尝试:统一 cache key
我想:如果 9 次调用的 cache key 不同,那统一它们就能复用了吧?
于是打了 4 轮 patch:
- superset checking:检查已加载的 registry 是否包含请求的全部 plugin
- full registry:始终传递
compatiblePluginIds: null,强制走 broad scope - broad scope bypass:跳过
hasExplicitCompatibilityInputs检查 - scope widening:合并现有和新请求的 plugin 集合
结果:都生效了,但总耗时几乎没变化。
5. 发现:“隐形开销”
重启 Gateway 后测试耗时 ~1 分钟,不重启直接测试 ~40 秒。
差距 ~20 秒来自 Gateway 重启本身的开销(launchctl unload/load + 进程冷启动)。这意味着之前所有“重启后测试”都引入了 ~20 秒噪音。
四、核心发现:3 个平台级痛点
痛点 1:Plugin 加载机制设计限制
4.29 的 plugin cache 是纯内存级的,没有跨进程持久化方案。每次 agent run 都要重新做:
- jiti 动态加载 JS 模块
- 扫描和遍历所有 bundled plugin
- 构建 runtime registry
这是架构层的设计选择,不是 bug,但确实影响了响应速度。
痛点 2:旧 patch 体系不可持续
4.29 的 hash-named ESM 构建产物意味着:
- 每次升级后文件名全变
- 函数名可能被 minify
- 代码结构可能重构
这使得基于正则/字符串匹配的自动化 patch 恢复脚本几乎肯定失败。如果你以前也有类似的自定义 patch,需要重新评估维护策略。
痛点 3:优化收益存在天花板
即使把 plugin 加载优化到极致,还有两个大头吃掉了时间:
- system-prompt(~6s):加载 skills、persona、构建 system prompt
- stream-setup(~6s):流初始化、transport 解析、API key 解析
这两个阶段的优化需要更深入的代码改动,风险收益比不高。
五、OpenClaw 已知痛点汇总
除了本次跟踪的性能问题,OpenClaw 还有一些长期存在的平台级痛点:
| 痛点 | 影响 | 对策 |
|---|---|---|
| Pricing fetch 阻塞 | 启动时从 OpenRouter 拉取定价,国内网络极慢 | 环境变量 OPENCLAW_SKIP_MODEL_PRICING=1 |
| bundled-runtime-deps 重入锁 | 插件加载时 CPU 100% 卡死 | 打 patch 加进程级锁检测 |
| plugin-runtime-deps 缓存失效 | 升级后每次重启都重装 63+ 个依赖 | 升级后清理旧缓存目录 |
| 内存占用 500MB+ | 115 个插件即使什么都不做也占 500MB | 定期 restart,无根治方案 |
| event-dispatch 启动慢 | 启动时串行初始化,30+ 秒才 ready | 启动后等 2 分钟再测 |
| 飞书 probe 超时 | 启动时 500ms 超时导致 bot 身份丢失 | 环境变量设 5000ms |
| WebSocket handshake 超时 | Safari 连接被断开 | 环境变量设 60000ms |
六、结论与建议
这次优化的收获
- 弄清楚了 4.29 的 plugin 加载机制(内存级 cache、跨进程不共享)
- 验证了“减少 plugin 数量”路径的收益天花板
- 验证了“统一 cache key”路径的收益天花板
- 发现了 Gateway 重启引入的 ~20s 测试噪音
建议
如果你也在用 OpenClaw,以下几点可能帮到你:
1. 不需要 Gateway 功能时,用 –local 模式
Gateway 模式 ~40s,Local 模式 ~15s,差距 ~24s。如果只是本地调试不需要消息投递,–local 是明智选择。
2. 升级前备份 patch,升级后重新评估
4.29 的 hash-named ESM 导致旧 patch 体系不可持续。升级前 stash 本地修改,升级后不要盲目相信自动恢复脚本,一定要人工检查是否仍有效。
3. 配置环境变量规避已知痛点
OPENCLAW_SKIP_MODEL_PRICING=1、OPENCLAW_FEISHU_STARTUP_PROBE_TIMEOUT_MS=5000、OPENCLAW_HANDSHAKE_TIMEOUT_MS=60000 这三个环境变量能解决大部分启动阶段的痛点。
技术栈的优化是没有终点的。这次跟踪不是为了打败某个耗时数字,而是为了弄清楚“痛点在哪里、为什么、能不能解”。有时候,知道什么不能做,比知道什么能做更有价值。
夜雨聆风