CLI 启动时间优化¶
重要性:⭐⭐(专项优化——启动时间从 1000ms 降到 300ms) 范围:
src/main.tsx启动路径 +utils/startupProfiler.js方法:基于 deep-dive-main.md + 启动 profiler + 顶部预取策略
1. 启动时间线(推测)¶
T+0ms Bun 启动
T+50ms 模块求值开始
T+100ms startMdmRawRead 触发
T+100ms startKeychainPrefetch 触发
T+200ms main.tsx 顶部副作用完成
T+250ms imports 完成
T+300ms main() 开始
T+300ms preAction: ensureMdmSettingsLoaded + ensureKeychainPrefetchCompleted
T+350ms preAction: init() 开始
T+400ms preAction: init() 完成
T+400ms preAction: initSinks
T+400ms preAction: runMigrations
T+400ms preAction: loadRemoteManagedSettings (fire-and-forget)
T+400ms run() 开始
T+500ms 第一个 render
T+500ms startDeferredPrefetches 触发(12 个 fire-and-forget)
总冷启动: ~500ms
~500ms 冷启动 —— 商业级。
2. 5 大启动优化¶
2.1 顶部并行预取(~200ms 节省)¶
// main.tsx 行 1-20
import { profileCheckpoint, profileReport } from './utils/startupProfiler.js';
profileCheckpoint('main_tsx_entry'); // 埋点
import { startMdmRawRead } from './utils/settings/mdm/rawRead.js';
startMdmRawRead(); // 立即触发(不 await)
import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js';
startKeychainPrefetch(); // 立即触发
3 个 fire-and-forget:
- profileCheckpoint —— 0ms
- startMdmRawRead —— 与 imports 并行 ~135ms
- startKeychainPrefetch —— 与 imports 并行 ~65ms (macOS)
节省 ~200ms。
2.2 print 模式 fast-path(~50ms 节省)¶
// main.tsx 行 3880
const isPrintMode = process.argv.includes('-p') || process.argv.includes('--print');
const isCcUrl = process.argv.some(a => a.startsWith('cc://') || a.startsWith('cc+unix://'));
if (isPrintMode && !isCcUrl) {
await program.parseAsync(process.argv);
return program; // 早返回
}
print 模式早返回 —— 不注册 20+ subcommand。
节省 ~50-100ms。
2.3 feature-gated 懒加载(~50ms 节省)¶
const coordinatorModeModule = feature('COORDINATOR_MODE')
? require('./coordinator/coordinatorMode.js')
: null;
编译时 DCE —— 商业版不打包这些模块。
节省 加载时间 + 解析时间。
2.4 首屏后预取(不阻塞首屏)¶
// startDeferredPrefetches
void initUser();
void getUserContext();
void getRelevantTips();
// ... 12 个 fire-and-forget
首屏后 才跑 —— 不阻塞 paint。
节省 ~200ms 首屏。
2.5 eager settings 加载(~50ms 节省)¶
早期加载 —— 后续 fast-path。
3. 30+ profileCheckpoint 全景¶
// main.tsx
profileCheckpoint('main_tsx_entry')
profileCheckpoint('main_function_start')
profileCheckpoint('main_warning_handler_initialized')
profileCheckpoint('main_client_type_determined')
profileCheckpoint('main_before_run')
profileCheckpoint('main_after_run')
// preAction
profileCheckpoint('preAction_start')
profileCheckpoint('preAction_after_mdm')
profileCheckpoint('preAction_after_init')
profileCheckpoint('preAction_after_sinks')
profileCheckpoint('preAction_after_migrations')
profileCheckpoint('preAction_after_remote_settings')
profileCheckpoint('preAction_after_settings_sync')
// run
profileCheckpoint('run_commander_initialized')
profileCheckpoint('run_before_parse')
profileCheckpoint('run_after_parse')
~16 个 profileCheckpoint in main.tsx —— 总计 30+ 全文。
每一个埋点 都能: - 计算耗时 - 上报 Statsig(sampled) - 控制台输出(debug 模式)
4. 启动模式 vs 启动时间¶
| 模式 | 冷启动 | 备注 |
|---|---|---|
| 完整 | ~500ms | 全部功能 |
--bare |
~300ms | 跳过预取 |
-p (print) |
~400ms | fast-path |
CLAUDE_CODE_EXIT_AFTER_FIRST_RENDER=1 |
~300ms | 仅测 perf |
--debug |
~600ms | debug 输出 |
| 缓存命中 | ~200ms | 第二次启动 |
6 种模式 —— 性能差异显著。
5. 启动阶段细节¶
5.1 Bun 单文件可执行¶
- 启动快(~50ms)
- 跨平台打包
- 内置 runtime
Bun 优势 —— 比 Node 启动快。
5.2 模块求值¶
ES import 静态 —— 顺序求值。
5.3 顶部副作用¶
fire-and-forget —— 不阻塞 imports。
5.4 main() 函数¶
// ~150ms
async function main() {
// 1. 安全 setup
// 2. argv 预处理
// 3. 启动 init
// 4. preAction 钩子
// 5. run()
}
5 步 —— 详见 deep-dive-main.md。
5.5 preAction 钩子¶
// ~50-100ms
program.hook('preAction', async thisCommand => {
// 1. 等待 MDM/keychain 预取完成
// 2. init()
// 3. 启动 sinks
// 4. 加载 inline plugins
// 5. 迁移
// 6. 加载远程设置
});
6 步 —— 详见 deep-dive-main.md。
5.6 run() commander¶
// ~100ms
async function run() {
// 1. Commander 初始化
// 2. preAction 钩子(已跑)
// 3. 选项定义 (~40 个)
// 4. default action (2801 行)
// 5. 20+ subcommand 注册
// 6. parseAsync
}
6 步。
6. 启动 vs 首次响应¶
| 阶段 | 时间 | 用户感知 |
|---|---|---|
| 冷启动 | ~500ms | 看到 UI |
| 用户输入 → API | ~500ms-1s | 看到流式输出 |
| 总 (TTFT) | ~1-1.5s | 第一个 token |
TTFT ~1-1.5s —— 商业级。
7. 启动 vs 内存¶
| 阶段 | 内存 |
|---|---|
| 刚启动 | ~50-100MB |
| 加载 plugins | +5-20MB/plugin |
| 加载 MCP | +5-10MB/server |
| 加载 skills | +2-5MB/skill |
| 大 session | ~200-500MB |
基础 50-100MB —— 合理。
8. 启动优化"反模式"检查¶
| 反模式 | 项目里有吗 |
|---|---|
| 同步阻塞 IO | ❌ 多数 fire-and-forget |
| 顶层 await | ❌ 全部 fire-and-forget |
| 启动时 deep import | ❌ 多用 lazy require |
| 启动时网络请求 | ⚠️ 有(fire-and-forget) |
| 启动时磁盘读 | ⚠️ 有(fire-and-forget) |
总体优化 —— 商业级。
9. 进一步优化方向¶
9.1 减少 main.tsx 体积¶
拆 subcommand —— 已在做。
9.2 减少 hooks 数量¶
合并 —— 待评估。
9.3 减少 utils 数量¶
合并 —— 待评估。
9.4 预编译 regex¶
预编译 —— 已有。
9.5 减少 console.log¶
生产环境 —— 已控制。
10. 启动 vs 功能¶
10.1 trade-off¶
4 维 trade-off —— 项目选功能多 + 兼容性广。
10.2 --bare 模式¶
用户可控 —— 速度 vs 功能的旋钮。
11. 关键洞察¶
11.1 5 大优化¶
顶部预取 + print fast-path + feature DCE + 预取 + eager settings。
11.2 30+ profileCheckpoint¶
可观测性 —— 启动全程埋点。
11.3 6 种启动模式¶
完整 / --bare / -p / perf / --debug / 缓存。
11.4 Bun 优势¶
比 Node 启动快 ~50ms。
11.5 5 阶段启动¶
Bun → imports → main → preAction → run。
11.6 顶部 fire-and-forget¶
不阻塞 imports —— 并行化。
11.7 preAction 集中 init¶
6 步统一初始化。
11.8 print fast-path 节省 50-100ms¶
大幅优化 print 模式。
11.9 eager settings 早期¶
后续 fast-path。
11.10 启动 vs 功能 trade-off¶
项目选功能多 —— 用户可用 --bare 加速。
12. 改进方向(更激进)¶
12.1 main.tsx → subcommand 拆分¶
拆 subcommand —— 已在做。
12.2 预连接(HTTP/2 / gRPC)¶
预连接 —— 减少首请求延迟。
12.3 预编译 plugin 缓存¶
预编译 —— 启动快。
12.4 snapshot 启动¶
Snapshot —— Bun 特性。
12.5 Worker 预热¶
Worker —— 主线程不阻塞。
13. 阅读建议¶
- 看
main.tsx行 1-100 —— 顶部预取 - 看
main.tsx行 3880 —— print fast-path - 看
startDeferredPrefetches—— 首屏后预取 - 看
utils/startupProfiler.js—— 埋点 - 看 preAction hook —— 6 步初始化
14. 与其他分析的关系¶
| 文件 | 关系 |
|---|---|
performance-history.md |
性能整体 |
architecture-history.md |
启动演进 |
dce-dead-code-elimination.md |
编译时优化 |
15. 总结¶
启动时间:~500ms(商业级) TTFT:~1-1.5s(商业级) 优化手段:5 大类 可观测性:30+ profileCheckpoint 用户可控:6 种启动模式
核心 insight: - 顶部 fire-and-forget —— 不阻塞 imports - print 早返回 —— fast-path - preAction 集中 —— 6 步统一 - DCE 编译时 —— 体积优化 - 30+ 埋点 —— 可观测性