performance-optimization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePerformance Optimization
性能优化
Overview
概述
Measure before optimizing. Performance work without measurement is guessing — and guessing leads to premature optimization that adds complexity without improving what matters. Profile first, identify the actual bottleneck, fix it, measure again. Optimize only what measurements prove matters.
优化前先测量。没有测量的性能工作全凭猜测——猜测会导致过早优化,徒增复杂度却无法真正改进核心指标。先做性能分析,找出实际瓶颈,修复后再次测量。仅优化那些测量数据证明确实需要优化的部分。
When to Use
适用场景
- Performance requirements exist in the spec (load time budgets, response time SLAs)
- Users or monitoring report slow behavior
- Core Web Vitals scores are below thresholds
- You suspect a change introduced a regression
- Building features that handle large datasets or high traffic
When NOT to use: Don't optimize before you have evidence of a problem. Premature optimization adds complexity that costs more than the performance it gains.
- 规范中明确了性能要求(加载时间预算、响应时间SLA)
- 用户或监控系统反馈运行缓慢
- Core Web Vitals得分低于阈值
- 你怀疑某次变更引入了性能回退
- 开发需要处理大量数据集或高流量的功能
不适用场景: 在没有问题证据之前不要优化。过早优化会增加复杂度,其成本远高于带来的性能收益。
Core Web Vitals Targets
Core Web Vitals指标目标
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
| 指标 | 优秀 | 需要优化 | 较差 |
|---|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
The Optimization Workflow
优化工作流
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 regression1. MEASURE → 用真实数据建立基准线
2. IDENTIFY → 找到实际瓶颈(而非假设的)
3. FIX → 解决特定瓶颈
4. VERIFY → 再次测量,确认优化效果
5. GUARD → 增加监控或测试防止性能回退Step 1: Measure
步骤1:测量
Frontend:
bash
undefined前端:
bash
undefinedLighthouse in Chrome DevTools (or CI)
Chrome DevTools中的Lighthouse(或CI中运行)
Chrome DevTools → Performance tab → Record
Chrome DevTools → 性能面板 → 录制
Chrome DevTools MCP → Performance trace
Chrome DevTools MCP → 性能追踪
Web Vitals library in code
代码中引入Web Vitals库
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
**Backend:**
```bashimport { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
**后端:**
```bashResponse time logging
响应时间日志
Application Performance Monitoring (APM)
应用性能监控(APM)
Database query logging with timing
带耗时统计的数据库查询日志
Simple timing
简单计时
console.time('db-query');
const result = await db.query(...);
console.timeEnd('db-query');
undefinedconsole.time('db-query');
const result = await db.query(...);
console.timeEnd('db-query');
undefinedWhere to Start Measuring
测量起始点选择
Use the symptom to decide what to measure first:
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根据症状决定优先测量的维度:
什么环节慢?
├── 首次页面加载
│ ├── 包体积过大? --> 测量包体积,检查代码拆分
│ ├── 服务端响应慢? --> 测量TTFB,检查API/数据库
│ └── 渲染阻塞资源? --> 检查网络瀑布流中的CSS/JS阻塞情况
├── 交互卡顿
│ ├── 点击后UI冻结? --> 分析主线程,查找长任务(>50ms)
│ ├── 表单输入延迟? --> 检查重渲染、受控组件开销
│ └── 动画掉帧? --> 检查布局抖动、强制回流
├── 导航后页面加载慢
│ ├── 数据加载问题? --> 测量API响应时间,检查瀑布流请求
│ └── 客户端渲染问题? --> 分析组件渲染时间,检查N+1请求
└── 后端 / API
├── 单个接口慢? --> 分析数据库查询,检查索引
├── 所有接口都慢? --> 检查连接池、内存、CPU
└── 偶发缓慢? --> 检查锁竞争、GC停顿、外部依赖Step 2: Identify the Bottleneck
步骤2:识别瓶颈
Common bottlenecks by category:
Frontend:
| Symptom | Likely Cause | Investigation |
|---|---|---|
| Slow LCP | Large images, render-blocking resources, slow server | Check network waterfall, image sizes |
| High CLS | Images without dimensions, late-loading content, font shifts | Check layout shift attribution |
| Poor INP | Heavy JavaScript on main thread, large DOM updates | Check long tasks in Performance trace |
| Slow initial load | Large bundle, many network requests | Check bundle size, code splitting |
Backend:
| Symptom | Likely Cause | Investigation |
|---|---|---|
| Slow API responses | N+1 queries, missing indexes, unoptimized queries | Check database query log |
| Memory growth | Leaked references, unbounded caches, large payloads | Heap snapshot analysis |
| CPU spikes | Synchronous heavy computation, regex backtracking | CPU profiling |
| High latency | Missing caching, redundant computation, network hops | Trace requests through the stack |
按类别划分的常见瓶颈:
前端:
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
| LCP慢 | 图片过大、渲染阻塞资源、服务端慢 | 检查网络瀑布流、图片体积 |
| CLS高 | 图片无尺寸、内容延迟加载、字体偏移 | 检查布局偏移归因 |
| INP差 | 主线程JavaScript执行压力大、DOM更新量过大 | 检查性能追踪中的长任务 |
| 首次加载慢 | 包体积大、网络请求过多 | 检查包体积、代码拆分 |
后端:
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
| API响应慢 | N+1查询、缺失索引、查询未优化 | 检查数据库查询日志 |
| 内存持续增长 | 引用泄露、无边界缓存、载荷过大 | 堆快照分析 |
| CPU突增 | 同步 heavy 计算、正则回溯 | CPU性能分析 |
| 高延迟 | 缺失缓存、冗余计算、网络跳数过多 | 全链路请求追踪 |
Step 3: Fix Common Anti-Patterns
步骤3:修复常见反模式
N+1 Queries (Backend)
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
// 反面案例: N+1 — 为每个任务单独查询所属用户
const tasks = await db.tasks.findMany();
for (const task of tasks) {
task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
}
// 正面案例: 带关联查询的单次请求
const tasks = await db.tasks.findMany({
include: { owner: true },
});Unbounded Data Fetching
无边界数据拉取
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' },
});typescript
// 反面案例: 拉取所有记录
const allTasks = await db.tasks.findMany();
// 正面案例: 带限制的分页查询
const tasks = await db.tasks.findMany({
take: 20,
skip: (page - 1) * 20,
orderBy: { createdAt: 'desc' },
});Missing Image Optimization (Frontend)
缺失图片优化(前端)
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"
/>html
<!-- 反面案例: 无尺寸、无懒加载、无响应式尺寸 -->
<img src="/hero.jpg" />
<!-- 正面案例: 响应式、懒加载、尺寸正确 -->
<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图片描述"
/>Unnecessary Re-renders (React)
不必要的重渲染(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>;
}tsx
// 反面案例: 每次渲染都创建新对象,导致子组件重渲染
function TaskList() {
return <TaskFilters options={{ sortBy: 'date', order: 'desc' }} />;
}
// 正面案例: 稳定的引用
const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const;
function TaskList() {
return <TaskFilters options={DEFAULT_OPTIONS} />;
}
// 给开销大的组件使用React.memo
const TaskItem = React.memo(function TaskItem({ task }: Props) {
return <div>{/* 开销大的渲染逻辑 */}</div>;
});
// 给开销大的计算使用useMemo
function TaskStats({ tasks }: Props) {
const stats = useMemo(() => calculateStats(tasks), [tasks]);
return <div>{stats.completed} / {stats.total}</div>;
}Large Bundle Size
包体积过大
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
// 反面案例: 导入整个库
import { format } from 'date-fns';
// 正面案例: 支持tree-shaking的导入(如果库支持)
import { format } from 'date-fns/format';
// 正面案例: 对不常用的重型功能做动态导入
const ChartLibrary = lazy(() => import('./ChartLibrary'));Missing Caching (Backend)
缺失缓存(后端)
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 minutestypescript
// 缓存读多改少的数据
const CACHE_TTL = 5 * 60 * 1000; // 5分钟
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缓存头
app.use('/static', express.static('public', {
maxAge: '1y', // 缓存1年
immutable: true, // 无需重新验证(文件名使用内容哈希)
}));
// API响应的Cache-Control
res.set('Cache-Control', 'public, max-age=300'); // 5分钟Performance Budget
性能预算
Set budgets and enforce them:
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: ≥ 90Enforce in CI:
bash
undefined设置预算并强制执行:
JavaScript包: gzip后 < 200KB(首次加载)
CSS: gzip后 < 50KB
图片: 首屏单张图片 < 200KB
字体: 总计 < 100KB
API响应时间: p95 < 200ms
可交互时间: 4G网络下 < 3.5s
Lighthouse性能得分: ≥ 90在CI中强制执行:
bash
undefinedBundle size check
包体积检查
npx bundlesize --config bundlesize.config.json
npx bundlesize --config bundlesize.config.json
Lighthouse CI
Lighthouse CI
npx lhci autorun
undefinednpx lhci autorun
undefinedCommon Rationalizations
常见误区
| Rationalization | Reality |
|---|---|
| "We'll optimize later" | Performance debt compounds. Fix obvious anti-patterns now, defer micro-optimizations. |
| "It's fast on my machine" | Your machine isn't the user's. Profile on representative hardware and networks. |
| "This optimization is obvious" | If you didn't measure, you don't know. Profile first. |
| "Users won't notice 100ms" | Research shows 100ms delays impact conversion rates. Users notice more than you think. |
| "The framework handles performance" | Frameworks prevent some issues but can't fix N+1 queries or oversized bundles. |
| 误区 | 实际情况 |
|---|---|
| "我们之后再优化" | 性能债务会越积越多。现在就修复明显的反模式,微优化可以延后。 |
| "我机器上跑得很快" | 你的机器不等于用户的设备。请在代表性的硬件和网络环境下分析。 |
| "这个优化效果是明显的" | 如果你没有测量,你就无法确认效果。先做性能分析。 |
| "用户不会注意到100ms的延迟" | 研究表明100ms的延迟就会影响转化率。用户的感知比你想象的更敏锐。 |
| "框架会处理性能问题" | 框架可以避免部分问题,但无法修复N+1查询或者过大的包体积。 |
Red Flags
危险信号
- Optimization without profiling data to justify it
- N+1 query patterns in data fetching
- List endpoints without pagination
- Images without dimensions, lazy loading, or responsive sizes
- Bundle size growing without review
- No performance monitoring in production
- and
React.memoeverywhere (overusing is as bad as underusing)useMemo
- 没有性能分析数据支撑的优化
- 数据拉取中存在N+1查询模式
- 列表接口没有分页
- 图片没有设置尺寸、懒加载或者响应式规格
- 包体积无审核持续增长
- 生产环境没有性能监控
- 到处使用和
React.memo(过度使用和不足使用危害一样大)useMemo
Verification
验证
After any performance-related change:
- Before and after measurements exist (specific numbers)
- The specific bottleneck is identified and addressed
- Core Web Vitals are within "Good" thresholds
- Bundle size hasn't increased significantly
- No N+1 queries in new data fetching code
- Performance budget passes in CI (if configured)
- Existing tests still pass (optimization didn't break behavior)
任何性能相关变更后:
- 留存变更前后的测量数据(具体数值)
- 特定瓶颈已被识别并修复
- Core Web Vitals处于「优秀」阈值范围内
- 包体积没有显著增长
- 新的数据拉取代码中没有N+1查询
- CI中的性能预算校验通过(如果已配置)
- 现有测试全部通过(优化没有破坏原有功能)