第十五章:性能考量

第十五章:性能考量

⚡ 本章分析 MiniClaw 的性能优化策略和考量。


15.1 并行 I/O 优化

15.1.1 Promise.all 使用

┌─────────────────────────────────────────────────────────────────────┐
│                    并行 I/O 优化                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  问题:顺序读取多个文件效率低下                                     │
│                                                                     │
│  ❌ 顺序读取(慢):                                                │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  const agents = await fs.readFile('AGENTS.md', 'utf-8');    │   │
│  │  const soul = await fs.readFile('SOUL.md', 'utf-8');        │   │
│  │  const identity = await fs.readFile('IDENTITY.md', 'utf-8');│   │
│  │  const user = await fs.readFile('USER.md', 'utf-8');        │   │
│  │  // 总耗时 = t1 + t2 + t3 + t4                              │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  ✅ 并行读取(快):                                                │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  const [agents, soul, identity, user] = await Promise.all([ │   │
│  │    fs.readFile('AGENTS.md', 'utf-8'),                       │   │
│  │    fs.readFile('SOUL.md', 'utf-8'),                         │   │
│  │    fs.readFile('IDENTITY.md', 'utf-8'),                     │   │
│  │    fs.readFile('USER.md', 'utf-8'),                         │   │
│  │  ]);                                                        │   │
│  │  // 总耗时 = max(t1, t2, t3, t4)                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  性能对比:                                                         │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  场景              顺序读取        并行读取        提升      │   │
│  │  ─────────────────────────────────────────────────────────  │   │
│  │  4 个文件          ~40ms           ~15ms          62%       │   │
│  │  8 个文件          ~80ms           ~18ms          77%       │   │
│  │  12 个文件         ~120ms          ~20ms          83%       │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.1.2 独立扫描并行化

┌─────────────────────────────────────────────────────────────────────┐
│                    独立扫描并行化                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  boot() 方法中的并行扫描:                                          │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  async boot(options: BootOptions): Promise<string> {        │   │
│  │    // 并行执行独立的 I/O 操作                                │   │
│  │    const [                                                  │   │
│  │      agents,                                                │   │
│  │      soul,                                                  │   │
│  │      identity,                                              │   │
│  │      user,                                                  │   │
│  │      tools,                                                 │   │
│  │      memory,                                                │   │
│  │      heartbeat,                                             │   │
│  │      entities,                                              │   │
│  │      skills,                                                │   │
│  │      dailyLog,                                              │   │
│  │      gitStatus,                                             │   │
│  │    ] = await Promise.all([                                  │   │
│  │      this.readFile('AGENTS.md'),                            │   │
│  │      this.readFile('SOUL.md'),                              │   │
│  │      this.readFile('IDENTITY.md'),                          │   │
│  │      this.readFile('USER.md'),                              │   │
│  │      this.readFile('TOOLS.md'),                             │   │
│  │      this.readFile('MEMORY.md'),                            │   │
│  │      this.readFile('HEARTBEAT.md'),                         │   │
│  │      this.entityStore.load(),                               │   │
│  │      this.skillCache.getAll(),                              │   │
│  │      this.loadDailyLog(),                                   │   │
│  │      this.getGitStatus(),                                   │   │
│  │    ]);                                                      │   │
│  │                                                             │   │
│  │    // 后续处理...                                            │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  并行化原则:                                                       │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  ✅ 无依赖关系的操作可以并行                                 │   │
│  │  ✅ 文件读取之间通常无依赖                                   │   │
│  │  ✅ Git 状态检查独立于文件读取                               │   │
│  │  ❌ 有依赖关系的操作必须顺序执行                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.2 缓存策略

15.2.1 技能缓存 TTL

┌─────────────────────────────────────────────────────────────────────┐
│                    技能缓存 TTL                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  SkillCache 实现带 TTL 的缓存机制:                                 │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  class SkillCache {                                         │   │
│  │    private cache: Map<string, SkillCacheEntry>;             │   │
│  │    private lastScanTime: number = 0;                        │   │
│  │    private readonly TTL_MS = 5000;  // 5 秒有效期           │   │
│  │                                                             │   │
│  │    async getAll(): Promise<Map<string, SkillCacheEntry>> {  │   │
│  │      const now = Date.now();                                │   │
│  │                                                             │   │
│  │      // 缓存有效,直接返回                                   │   │
│  │      if (now - this.lastScanTime < this.TTL_MS) {           │   │
│  │        return this.cache;                                   │   │
│  │      }                                                      │   │
│  │                                                             │   │
│  │      // 缓存过期,重新扫描                                   │   │
│  │      await this.refresh();                                  │   │
│  │      return this.cache;                                     │   │
│  │    }                                                        │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  TTL 选择考量:                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  TTL 太短(如 1s)                                           │   │
│  │  ❌ 频繁扫描文件系统                                         │   │
│  │  ❌ 性能下降                                                 │   │
│  │                                                             │   │
│  │  TTL 太长(如 60s)                                          │   │
│  │  ❌ 新技能不能及时被发现                                     │   │
│  │  ❌ 数据可能过时                                             │   │
│  │                                                             │   │
│  │  TTL 适中(5s)                                              │   │
│  │  ✅ 平衡性能和实时性                                         │   │
│  │  ✅ 适合大多数使用场景                                       │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.2.2 缓存失效机制

┌─────────────────────────────────────────────────────────────────────┐
│                    缓存失效机制                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  手动失效场景:                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  class SkillCache {                                         │   │
│  │    // 手动使缓存失效                                         │   │
│  │    invalidate(): void {                                     │   │
│  │      this.lastScanTime = 0;                                 │   │
│  │    }                                                        │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  │  // 使用场景                                                 │   │
│  │  // 1. 新增技能后                                            │   │
│  │  kernel.skillCache.invalidate();                            │   │
│  │                                                             │   │
│  │  // 2. 删除技能后                                            │   │
│  │  kernel.skillCache.invalidate();                            │   │
│  │                                                             │   │
│  │  // 3. 技能内容更新后                                        │   │
│  │  kernel.skillCache.invalidate();                            │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  自动失效时机:                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  • TTL 过期自动刷新                                         │   │
│  │  • 服务重启时缓存清空                                       │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.3 Token 预算管理

15.3.1 预算分配策略

┌─────────────────────────────────────────────────────────────────────┐
│                    Token 预算分配策略                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  默认预算:8000 Token(可配置)                                     │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  段落优先级分配:                                            │   │
│  │                                                             │   │
│  │  优先级    内容类型              预算占比                    │   │
│  │  ─────────────────────────────────────────────────────────  │   │
│  │  1 (最高)  AGENTS.md            必须包含                    │   │
│  │  2         SOUL.md              必须包含                    │   │
│  │  3         IDENTITY.md          必须包含                    │   │
│  │  4         USER.md              高优先                      │   │
│  │  5         TOOLS.md             高优先                      │   │
│  │  6         MEMORY.md            中优先(仅主会话)           │   │
│  │  7         工作空间感知          中优先                      │   │
│  │  8         每日日志              低优先                      │   │
│  │  9         实体概览              低优先                      │   │
│  │  10 (最低) 技能索引              可截断                      │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  预算编译流程:                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  function compileWithBudget(                                │   │
│  │    sections: ContextSection[],                              │   │
│  │    budget: number                                           │   │
│  │  ): string {                                                │   │
│  │    // 1. 按优先级排序                                        │   │
│  │    sections.sort((a, b) => a.priority - b.priority);        │   │
│  │                                                             │   │
│  │    // 2. 依次添加段落                                        │   │
│  │    let result = '';                                         │   │
│  │    let usedTokens = 0;                                      │   │
│  │                                                             │   │
│  │    for (const section of sections) {                        │   │
│  │      const tokens = estimateTokens(section.content);        │   │
│  │                                                             │   │
│  │      if (usedTokens + tokens <= budget) {                   │   │
│  │        result += section.content;                           │   │
│  │        usedTokens += tokens;                                │   │
│  │      } else {                                               │   │
│  │        // 预算不足,截断或跳过                               │   │
│  │        break;                                               │   │
│  │      }                                                      │   │
│  │    }                                                        │   │
│  │                                                             │   │
│  │    return result;                                           │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.3.2 截断策略

┌─────────────────────────────────────────────────────────────────────┐
│                    截断策略                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  不同内容的截断策略:                                               │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  内容类型          截断策略              说明                │   │
│  │  ─────────────────────────────────────────────────────────  │   │
│  │  AGENTS.md         不截断                宪法级别            │   │
│  │  SOUL.md           不截断                核心人格            │   │
│  │  IDENTITY.md       不截断                身份必需            │   │
│  │  USER.md           保留最新偏好          按时间截断          │   │
│  │  MEMORY.md         保留最重要记忆        按重要性截断        │   │
│  │  每日日志          保留最近条目          按时间截断          │   │
│  │  技能索引          完全截断              预算不足时跳过      │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  日志截断示例:                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  // 保留最近 N 条日志条目                                    │   │
│  │  function truncateLog(log: string, maxEntries: number): string {│
│  │    const entries = log.split('\n---\n');                     │   │
│  │    const truncated = entries.slice(-maxEntries);             │   │
│  │    return truncated.join('\n---\n');                         │   │
│  │  }                                                          │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.3.3 Token 估算

┌─────────────────────────────────────────────────────────────────────┐
│                    Token 估算                                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  简化估算方法:                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  // 经验值:约 4 个字符 = 1 Token                            │   │
│  │  const CHARS_PER_TOKEN = 4;                                 │   │
│  │                                                             │   │
│  │  function estimateTokens(content: string): number {         │   │
│  │    return Math.ceil(content.length / CHARS_PER_TOKEN);      │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  │  // 示例                                                     │   │
│  │  const text = "Hello, World!";  // 13 字符                  │   │
│  │  const tokens = estimateTokens(text);  // ~4 tokens         │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  更精确的估算(可选):                                             │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  // 使用 tiktoken 库(更精确但更慢)                         │   │
│  │  import { encode } from 'tiktoken';                         │   │
│  │                                                             │   │
│  │  function countTokensExact(content: string): number {       │   │
│  │    return encode(content).length;                           │   │
│  │  }                                                          │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  MiniClaw 选择简化估算的原因:                                      │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  ✅ 无额外依赖                                               │   │
│  │  ✅ 计算速度快                                               │   │
│  │  ✅ 估算足够准确                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.4 启动性能

15.4.1 启动流程优化

┌─────────────────────────────────────────────────────────────────────┐
│                    启动流程优化                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  启动时间分解:                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  阶段                  耗时         优化措施                 │   │
│  │  ─────────────────────────────────────────────────────────  │   │
│  │  文件扫描              ~10ms        并行读取                 │   │
│  │  实体加载              ~5ms         懒加载                   │   │
│  │  技能发现              ~15ms        缓存 TTL                 │   │
│  │  上下文编译            ~5ms         预算控制                 │   │
│  │  ─────────────────────────────────────────────────────────  │   │
│  │  总计                  ~35ms                                 │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  懒加载策略:                                                       │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  class EntityStore {                                        │   │
│  │    private loaded: boolean = false;                         │   │
│  │                                                             │   │
│  │    private async ensureLoaded(): Promise<void> {            │   │
│  │      if (!this.loaded) {                                    │   │
│  │        await this.load();                                   │   │
│  │      }                                                      │   │
│  │    }                                                        │   │
│  │                                                             │   │
│  │  async query(name: string): Promise<Entity | null> {        │   │
│  │    await this.ensureLoaded();  // 首次使用时加载            │   │
│  │    return this.entities.find(e => e.name === name);         │   │
│  │  }                                                          │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

15.4.2 会话延续检测

┌─────────────────────────────────────────────────────────────────────┐
│                    会话延续检测                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  检测会话是否延续,避免重复加载:                                   │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                                                             │   │
│  │  interface SessionContinuation {                            │   │
│  │    isContinuation: boolean;                                 │   │
│  │    lastActivity: Date;                                      │   │
│  │    minutesSinceLastActivity: number;                        │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  │  function detectSessionContinuation(state: MiniClawState): SessionContinuation {│
│  │    const lastActivity = new Date(state.lastActivityTime);   │   │
│  │    const now = new Date();                                  │   │
│  │    const minutesSince = (now.getTime() - lastActivity.getTime()) / (1000 * 60);│
│  │                                                             │   │
│  │    return {                                                 │   │
│  │      isContinuation: minutesSince < 30,  // 30 分钟内       │   │
│  │      lastActivity,                                          │   │
│  │      minutesSinceLastActivity: minutesSince,                │   │
│  │    };                                                       │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  │  // 会话延续时,使用精简上下文                               │   │
│  │  if (continuation.isContinuation) {                         │   │
│  │    // 跳过完整加载,仅加载增量                               │   │
│  │    return generateMinimalContext();                         │   │
│  │  }                                                          │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  性能收益:                                                         │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  完整启动:~35ms                                             │   │
│  │  延续会话:~10ms                                             │   │
│  │  提升:~70%                                                  │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

本章小结

┌─────────────────────────────────────────────────────────────────────┐
│                     第十五章 核心要点                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ⚡ 并行 I/O                                                        │
│     • Promise.all 并行读取文件                                      │
│     • 独立操作并行化                                                │
│     • 性能提升 60%+                                                 │
│                                                                     │
│  💾 缓存策略                                                        │
│     • SkillCache 带 TTL 缓存(5 秒)                                │
│     • 手动失效机制                                                  │
│                                                                     │
│  📊 Token 预算                                                      │
│     • 默认 8000 Token(可配置)                                     │
│     • 优先级分配策略                                                │
│     • 智能截断策略                                                  │
│                                                                     │
│  🚀 启动性能                                                        │
│     • 总启动时间 ~35ms                                              │
│     • 懒加载策略                                                    │
│     • 会话延续检测                                                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

下一章:扩展性设计 →

← 返回目录