# Performance Optimization

> 优化应用性能。适用于 spec 中存在性能要求、怀疑有性能回退、需要改善 Core Web Vitals 或加载时间时，也适用于 profiling 已经发现瓶颈、需要动手修复的时候。

- Skill: `233i/performance-optimization` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 233i/performance-optimization`
- Raw SKILL.md: https://api.skillmd.com/api/skills/233i/performance-optimization/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: 233i (https://skillmd.com/u/233i)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/233i/performance-optimization

---


# 性能优化

## 概览

优化前先测量。没有测量的性能工作，本质上是在猜，而猜测只会带来过早优化，让复杂度上升却没改善真正重要的地方。先做 profiling，找到真实瓶颈，再修它，再测一次。只优化那些被数据证明值得优化的地方。

## 何时使用

- Spec 里有性能要求，例如加载预算、响应时间 SLA
- 用户或监控反馈页面 / 接口变慢
- Core Web Vitals 低于阈值
- 你怀疑某个改动带来了性能回退
- 正在做处理大数据集或高流量的功能

**不适用的场景：** 在没有证据前不要优化。过早优化会增加复杂度，代价往往高于它带来的收益。

## Core Web Vitals 目标

| 指标 | Good | Needs Improvement | Poor |
|--------|------|-------------------|------|
| **LCP** | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| **INP** | ≤ 200ms | ≤ 500ms | > 500ms |
| **CLS** | ≤ 0.1 | ≤ 0.25 | > 0.25 |

## 优化工作流

```
1. MEASURE  → Establish baseline with real data
2. IDENTIFY → Find the actual bottleneck (not assumed)
3. FIX      → Address the specific bottleneck
4. VERIFY   → Measure again, confirm improvement
5. GUARD    → Add monitoring or tests to prevent regression
```

### 步骤 1：测量

**前端：**
```bash
# Lighthouse in Chrome DevTools (or CI)
# Chrome DevTools → Performance tab → Record
# Chrome DevTools MCP → Performance trace

# Web Vitals library in code
import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP(console.log);
onINP(console.log);
onCLS(console.log);
```

**后端：**
```bash
# Response time logging
# Application Performance Monitoring (APM)
# Database query logging with timing

# Simple timing
console.time('db-query');
const result = await db.query(...);
console.timeEnd('db-query');
```

### 从哪里开始测

根据“慢在哪里”来决定先测什么：

```
What is slow?
├── First page load
│   ├── Large bundle? --> Measure bundle size, check code splitting
│   ├── Slow server response? --> Measure TTFB, check API/database
│   └── Render-blocking resources? --> Check network waterfall for CSS/JS blocking
├── Interaction feels sluggish
│   ├── UI freezes on click? --> Profile main thread, look for long tasks (>50ms)
│   ├── Form input lag? --> Check re-renders, controlled component overhead
│   └── Animation jank? --> Check layout thrashing, forced reflows
├── Page after navigation
│   ├── Data loading? --> Measure API response times, check for waterfalls
│   └── Client rendering? --> Profile component render time, check for N+1 fetches
└── Backend / API
    ├── Single endpoint slow? --> Profile database queries, check indexes
    ├── All endpoints slow? --> Check connection pool, memory, CPU
    └── Intermittent slowness? --> Check for lock contention, GC pauses, external deps
```

### 步骤 2：找到真正瓶颈

按类别看常见瓶颈：

**前端：**

| 症状 | 常见原因 | 如何调查 |
|---------|---------|----------|
| LCP 慢 | 大图、阻塞渲染资源、慢服务端 | 看 network waterfall、图片尺寸 |
| CLS 高 | 图片没尺寸、晚到的内容、字体抖动 | 看 layout shift attribution |
| INP 差 | 主线程 JS 过重、大块 DOM 更新 | 看 Performance trace 中的长任务 |
| 首屏加载慢 | bundle 太大、请求太多 | 看 bundle size 与 code splitting |

**后端：**

| 症状 | 常见原因 | 如何调查 |
|---------|---------|----------|
| API 响应慢 | N+1 查询、缺索引、查询未优化 | 看数据库查询日志 |
| 内存增长 | 引用泄漏、无界缓存、大 payload | 做 heap snapshot 分析 |
| CPU 峰值 | 大量同步计算、正则回溯 | 做 CPU profiling |
| 延迟高 | 缺缓存、重复计算、网络跳点多 | 追踪整条请求链 |

### 步骤 3：修常见反模式

#### N+1 查询（后端）

```typescript
// BAD: N+1 — one query per task for the owner
const tasks = await db.tasks.findMany();
for (const task of tasks) {
  task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
}

// GOOD: Single query with join/include
const tasks = await db.tasks.findMany({
  include: { owner: true },
});
```

#### 无界数据抓取

```typescript
// BAD: Fetching all records
const allTasks = await db.tasks.findMany();

// GOOD: Paginated with limits
const tasks = await db.tasks.findMany({
  take: 20,
  skip: (page - 1) * 20,
  orderBy: { createdAt: 'desc' },
});
```

#### 图片没有优化（前端）

```html
<!-- BAD: No dimensions, no lazy loading, no responsive sizes -->
<img src="/hero.jpg" />

<!-- GOOD: Responsive, lazy-loaded, properly sized -->
<img
  src="/hero.jpg"
  srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
  sizes="(max-width: 768px) 100vw, 50vw"
  width="1200"
  height="600"
  loading="lazy"
  alt="Hero image description"
/>
```

#### 不必要的重复渲染（React）

```tsx
// BAD: Creates new object on every render, causing children to re-render
function TaskList() {
  return <TaskFilters options={{ sortBy: 'date', order: 'desc' }} />;
}

// GOOD: Stable reference
const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const;
function TaskList() {
  return <TaskFilters options={DEFAULT_OPTIONS} />;
}

// Use React.memo for expensive components
const TaskItem = React.memo(function TaskItem({ task }: Props) {
  return <div>{/* expensive render */}</div>;
});

// Use useMemo for expensive computations
function TaskStats({ tasks }: Props) {
  const stats = useMemo(() => calculateStats(tasks), [tasks]);
  return <div>{stats.completed} / {stats.total}</div>;
}
```

#### Bundle 过大

```typescript
// BAD: Importing entire library
import { format } from 'date-fns';

// GOOD: Tree-shakable import (if the library supports it)
import { format } from 'date-fns/format';

// GOOD: Dynamic import for heavy, rarely-used features
const ChartLibrary = lazy(() => import('./ChartLibrary'));
```

#### 缺少缓存（后端）

```typescript
// Cache frequently-read, rarely-changed data
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
let cachedConfig: AppConfig | null = null;
let cacheExpiry = 0;

async function getAppConfig(): Promise<AppConfig> {
  if (cachedConfig && Date.now() < cacheExpiry) {
    return cachedConfig;
  }
  cachedConfig = await db.config.findFirst();
  cacheExpiry = Date.now() + CACHE_TTL;
  return cachedConfig;
}

// HTTP caching headers for static assets
app.use('/static', express.static('public', {
  maxAge: '1y',           // Cache for 1 year
  immutable: true,        // Never revalidate (use content hashing in filenames)
}));

// Cache-Control for API responses
res.set('Cache-Control', 'public, max-age=300'); // 5 minutes
```

## 性能预算

先设预算，再把它纳入门禁：

```
JavaScript bundle: < 200KB gzipped (initial load)
CSS: < 50KB gzipped
Images: < 200KB per image (above the fold)
Fonts: < 100KB total
API response time: < 200ms (p95)
Time to Interactive: < 3.5s on 4G
Lighthouse Performance score: ≥ 90
```

**在 CI 中强制执行：**
```bash
# Bundle size check
npx bundlesize --config bundlesize.config.json

# Lighthouse CI
npx lhci autorun
```

## 常见自我安慰

| 自我安慰 | 现实 |
|---|---|
| “以后再优化” | 性能债会持续累积。明显反模式现在就该修，微优化可以推后。 |
| “我机器上挺快的” | 你的机器不是用户的机器。要在代表性的硬件和网络条件下测。 |
| “这个优化一看就对” | 没测过就不知道。先 profile。 |
| “用户感觉不到 100ms” | 研究表明，100ms 的延迟就会影响转化。用户比你想象中敏感。 |
| “框架会帮我们处理性能” | 框架只能挡住一部分问题，N+1 查询和超大 bundle 还是得你自己解决。 |

## 危险信号

- 没有 profiling 数据就开始优化
- 数据获取里有 N+1 查询
- 列表 endpoint 没有分页
- 图片没有尺寸、懒加载或响应式资源
- Bundle 持续膨胀却没人 review
- 生产环境没有性能监控
- 到处乱用 `React.memo` 和 `useMemo`，过度使用和完全不用一样糟

## 验证

做完任何性能相关改动后，确认：

- [ ] 有明确的前后对比数据
- [ ] 已找出并修复具体瓶颈
- [ ] Core Web Vitals 达到 “Good” 标准
- [ ] Bundle size 没有显著变大
- [ ] 新的数据获取代码里没有 N+1 查询
- [ ] 如果项目已配置，性能预算在 CI 中通过
- [ ] 现有测试仍然通过，没有因为优化破坏行为

