性能分析与优化
概览
对代码执行进行性能分析,以数据驱动的方式识别性能瓶颈并优化关键路径。
何时使用
- 性能优化
- 识别CPU瓶颈
- 优化热点路径
- 调查慢请求
- 降低延迟
- 提高吞吐量
实施示例
1. Node.js性能分析
import { performance, PerformanceObserver } from 'perf_hooks';
class Profiler {
private marks = new Map<string, number>();
mark(name: string): void {
this.marks.set(name, performance.now());
}
measure(name: string, startMark: string): number {
const start = this.marks.get(startMark);
if (!start) throw new Error(`Mark ${startMark} not found`);
const duration = performance.now() - start;
console.log(`${name}: ${duration.toFixed(2)}ms`);
return duration;
}
async profile<T>(name: string, fn: () => Promise<T>): Promise<T> {
const start = performance.now();
try {
return await fn();
} finally {
const duration = performance.now() - start;
console.log(`${name}: ${duration.toFixed(2)}ms`);
}
}
}
// 使用方法
const profiler = new Profiler();
app.get('/api/users', async (req, res) => {
profiler.mark('request-start');
const users = await profiler.profile('fetch-users', async () => {
return await db.query('SELECT * FROM users');
});
profiler.measure('total-request-time', 'request-start');
res.json(users);
});
2. Chrome DevTools CPU性能分析
import inspector from 'inspector';
import fs from 'fs';
class CPUProfiler {
private session: inspector.Session | null = null;
start(): void {
this.session = new inspector.Session();
this.session.connect();
this.session.post('Profiler.enable');
this.session.post('Profiler.start');
console.log('CPU性能分析已开始');
}
async stop(outputFile: string): Promise<void> {
if (!this.session) return;
this.session.post('Profiler.stop', (err, { profile }) => {
if (err) {
console.error('性能分析错误:', err);
return;
}
fs.writeFileSync(outputFile, JSON.stringify(profile));
console.log(`性能分析文件已保存至 ${outputFile}`);
this.session!.disconnect();
this.session = null;
});
}
}
// 使用方法
const cpuProfiler = new CPUProfiler();
// 开始性能分析
cpuProfiler.start();
// 运行代码进行性能分析
await runExpensiveOperation();
// 停止并保存
await cpuProfiler.stop('./profile.cpuprofile');
3. Python cProfile
import cProfile
import pstats
from pstats import SortKey
import io
class Profiler:
def __init__(self):
self.profiler = cProfile.Profile()
def __enter__(self):
self.profiler.enable()
return self
def __exit__(self, *args):
self.profiler.disable()
def print_stats(self, sort_by: str = 'cumulative'):
"""打印性能分析统计信息。"""
s = io.StringIO()
ps = pstats.Stats(self.profiler, stream=s)
if sort_by == 'time':
ps.sort_stats(SortKey.TIME)
elif sort_by == 'cumulative':
ps.sort_stats(SortKey.CUMULATIVE)
elif sort_by == 'calls':
ps.sort_stats(SortKey.CALLS)
ps.print_stats(20) # 显示前20条
print(s.getvalue())
def save_stats(self, filename: str):
"""保存性能分析数据。"""
self.profiler.dump_stats(filename)
# 使用方法
with Profiler() as prof:
# 代码进行性能分析
result = expensive_function()
prof.print_stats('cumulative')
prof.save_stats('profile.prof')
4. 基准测试
class Benchmark {
async run(
name: string,
fn: () => Promise<any>,
iterations: number = 1000
): Promise<void> {
console.log(`
基准测试: ${name}`);
const times: number[] = [];
// 预热
for (let i = 0; i < 10; i++) {
await fn();
}
// 实际基准测试
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await fn();
times.push(performance.now() - start);
}
// 统计数据
const sorted = times.sort((a, b) => a - b);
const min = sorted[0];
const max = sorted[sorted.length - 1];
const avg = times.reduce((a, b) => a + b, 0) / times.length;
const p50 = sorted[Math.floor(sorted.length * 0.5)];
const p95 = sorted[Math.floor(sorted.length * 0.95)];
const p99 = sorted[Math.floor(sorted.length * 0.99)];
console.log(` 迭代次数: ${iterations}`);
console.log(` 最小值: ${min.toFixed(2)}ms`);
console.log(` 最大值: ${max.toFixed(2)}ms`);
console.log(` 平均值: ${avg.toFixed(2)}ms`);
console.log(` P50: ${p50.toFixed(2)}ms`);
console.log(` P95: ${p95.toFixed(2)}ms`);
console.log(` P99: ${p99.toFixed(2)}ms`);
}
async compare(
implementations: Array<{ name: string; fn: () => Promise<any> }>,
iterations: number = 1000
): Promise<void> {
for (const impl of implementations) {
await this.run(impl.name, impl.fn, iterations);
}
}
}
// 使用方法
const bench = new Benchmark();
await bench.compare([
{
name: 'Array.filter + map',
fn: async () => {
const arr = Array.from({ length: 1000 }, (_, i) => i);
return arr.filter(x => x % 2 === 0).map(x => x * 2);
}
},
{
name: '单循环',
fn: async () => {
const arr = Array.from({ length: 1000 }, (_, i) => i);
const result = [];
for (const x of arr) {
if (x % 2 === 0) {
result.push(x * 2);
}
}
return result;
}
}
]);
5. 数据库查询性能分析
import { Pool } from 'pg';
class QueryProfiler {
constructor(private pool: Pool) {}
async profileQuery(query: string, params: any[] = []): Promise<{
result: any;
planningTime: number;
executionTime: number;
plan: any;
}> {
// 启用计时
await this.pool.query('SET track_io_timing = ON');
// 获取查询计划
const explainResult = await this.pool.query(
`EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${query}`,
params
);
const plan = explainResult.rows[0]['QUERY PLAN'][0];
// 执行实际查询
const start = performance.now();
const result = await this.pool.query(query, params);
const duration = performance.now() - start;
return {
result: result.rows,
planningTime: plan['Planning Time'],
executionTime: plan['Execution Time'],
plan
};
}
formatPlan(plan: any): string {
let output = '查询计划:
';
output += `规划时间: ${plan['Planning Time']}ms
`;
output += `执行时间: ${plan['Execution Time']}ms
`;
const formatNode = (node: any, indent: number = 0) => {
const prefix = ' '.repeat(indent);
output += `${prefix}${node['Node Type']}
`;
output += `${prefix} 成本: ${node['Total Cost']}
`;
output += `${prefix} 行数: ${node['Actual Rows']}
`;
output += `${prefix} 时间: ${node['Actual Total Time']}ms
`;
if (node.Plans) {
node.Plans.forEach((child: any) => formatNode(child, indent + 1));
}
};
formatNode(plan.Plan);
return output;
}
}
// 使用方法
const profiler = new QueryProfiler(pool);
const { result, planningTime, executionTime, plan } = await profiler.profileQuery(
'SELECT * FROM users WHERE age > $1',
[25]
);
console.log(profiler.formatPlan(plan));
6. 火焰图生成
# 使用0x生成火焰图
npx 0x -o flamegraph.html node server.js
# 或者使用clinic.js
npx clinic doctor --on-port 'autocannon localhost:3000' -- node server.js
npx clinic flame --on-port 'autocannon localhost:3000' -- node server.js
优化技术
1. 缓存
class LRUCache<K, V> {
private cache = new Map<K, V>();
private maxSize: number;
constructor(maxSize: number = 100) {
this.maxSize = maxSize;
}
get(key: K): V | undefined {
if (!this.cache.has(key)) return undefined;
// 移动到末尾(最近使用)
const value = this.cache.get(key)!;
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key: K, value: V): void {
// 如果存在则移除
if (this.cache.has(key)) {
this.cache.delete(key);
}
// 添加到末尾
this.cache.set(key, value);
// 超过容量则逐出最旧的
if (this.cache.size > this.maxSize) {
const oldest = this.cache.keys().next().value;
this.cache.delete(oldest);
}
}
}
2. 懒加载
class LazyValue<T> {
private value?: T;
private loaded = false;
constructor(private loader: () => T) {}
get(): T {
if (!this.loaded) {
this.value = this.loader();
this.loaded = true;
}
return this.value!;
}
}
// 使用方法
const expensive = new LazyValue(() => {
console.log('计算昂贵的值...');
return computeExpensiveValue();
});
// 仅在首次访问时计算
const value = expensive.get();
最佳实践
✅ 执行
- 优化前进行性能分析
- 专注于热点路径
- 测量变更的影响
- 使用类似生产环境的数据
- 考虑内存与速度的权衡
- 文档化优化理由
❌ 不执行
- 无性能分析的优化
- 为了小的性能提升牺牲可读性
- 跳过基准测试
- 优化冷路径
- 无测量的变更
工具
- Node.js: 0x, clinic.js, node --prof
- Python: cProfile, py-spy, memory_profiler
- 可视化: 火焰图,Chrome DevTools
- 数据库: EXPLAIN ANALYZE, pg_stat_statements