代码热点优化
代码热点是 CPU 时间密集的代码段,通过火焰图和性能剖析定位并优化。
性能剖析工具
Node.js 内置 profiler
JavaScript
// 启动 profiler
node --prof app.js
// 运行一段时间后生成日志
// 分析 isolate-*.log
# 处理日志
node --prof-process isolate-*.log > processed.txt
输出分析:
JavaScript
[Summary]:
ticks: 10000
total: 1000ms
javascript: 60% ← JS 执行占比
c++: 30% ← C++ 执行占比
gc: 10% ← GC 占比
[JavaScript]:
ticks function
2000 processData ← 热点函数
1500 calculate
1000 parseJSON
使用 performance API
JavaScript
const { performance, PerformanceObserver } = require('perf_hooks');
// 监控函数执行
const obs = new PerformanceObserver((list) => {
const entries = list.getEntriesByName('myFunction');
entries.forEach(entry => {
console.log(`Duration: ${entry.duration.toFixed(2)}ms`);
});
});
obs.observe({ entryTypes: ['function'] });
// 测量函数
function hotFunction() {
performance.mark('start');
// ... 代码 ...
performance.mark('end');
performance.measure('hotFunction', 'start', 'end');
}
V8 Inspector profiling
Bash
const inspector = require('inspector');
const fs = require('fs');
const session = new inspector.Session();
session.connect();
session.post('Profiler.enable');
session.post('Profiler.start');
// 运行一段时间
setTimeout(() => {
session.post('Profiler.stop', (err, { profile }) => {
fs.writeFileSync('profile.cpuprofile', JSON.stringify(profile));
session.disconnect();
});
}, 10000);
火焰图分析
生成火焰图
JavaScript
# 方法1:clinic.js flame
npm install -g clinic
clinic flame -- node app.js
# 方法2:0x 工具
npm install -g 0x
0x app.js
# 方法3:手动生成
node --prof app.js
node --prof-process isolate-*.log > profile.txt
# 使用 flamegraph.pl 生成 SVG
火焰图解读
JavaScript
横轴:时间占比(不是时间顺序)
纵轴:调用栈深度
宽度:函数占用 CPU 时间
颜色:无特殊意义,区分不同函数
解读方法:
1. 找最宽的平台 → CPU 热点
2. 看调用栈 → 谁调用了热点
3. 分析热点代码 → 定位优化点
火焰图示例
JavaScript
processData ────────────────────────────
┌──────────────────────────────────────┐
│ calculate │
│ ┌──────────────────────────┐ │
│ │ parseJSON │ │ ← 最宽:热点
│ │ ┌──────────────────┐ │ │
│ │ │ iterateArray │ │ │
│ │ │ ┌────────────┐ │ │ │
│ │ │ │ toString │ │ │ │
│ │ │ └────────────┘ │ │ │
│ │ └──────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────────────┘
常见热点优化
1. 循环优化
JavaScript
// 热点:低效循环
for (let i = 0; i < array.length; i++) {
if (array[i].status === 'active') {
result.push(array[i].name.toUpperCase());
}
}
// 优化:减少属性访问
for (let i = 0, len = array.length; i < len; i++) {
const item = array[i];
if (item.status === 'active') {
result.push(item.name.toUpperCase());
}
}
// 优化:提前过滤
const activeItems = array.filter(item => item.status === 'active');
const names = activeItems.map(item => item.name.toUpperCase());
2. 对象属性优化
JavaScript
// 热点:动态属性导致 Hidden Class 变化
function process(obj) {
obj.temp = 'value'; // 添加新属性
// V8 无法优化,每次创建新 Hidden Class
}
// 优化:固定属性结构
class DataItem {
constructor(name, value) {
this.name = name;
this.value = value;
this.temp = null; // 声明时预留
}
}
function process(item) {
item.temp = 'value'; // 使用预留属性
}
3. 数组操作优化
JavaScript
// 烕点:稀疏数组
const arr = [];
arr[1000] = 'value'; // 稀疏数组,V8 退化
// 优化:紧凑数组
const arr = new Array(1001);
for (let i = 0; i < 1001; i++) arr[i] = null;
arr[1000] = 'value';
// 烕点:类型混合数组
const arr = [1, 'string', {}, null]; // 类型混合
// 优化:同类型数组
const numbers = [1, 2, 3, 4];
const strings = ['a', 'b', 'c'];
4. 字符串拼接
Bash
// 烕点:循环内拼接
let result = '';
for (let i = 0; i < 10000; i++) {
result += strings[i]; // 每次创建新字符串
}
// 优化:数组 join
const parts = [];
for (let i = 0; i < 10000; i++) {
parts.push(strings[i]);
}
const result = parts.join('');
// 优化:模板字符串(简单场景)
const result = `${str1}${str2}${str3}`;
5. JSON 处理
JavaScript
// 烕点:重复解析
function handler(data) {
const parsed = JSON.parse(data); // 每次解析
return parsed.id;
}
// 优化:缓存解析结果
const cache = new Map();
function cachedParse(data) {
if (cache.has(data)) {
return cache.get(data);
}
const parsed = JSON.parse(data);
cache.set(data, parsed);
return parsed;
}
// 烕点:大数据 JSON
const data = JSON.parse(largeJsonString); // 阻塞
// 优化:流式解析
const JSONStream = require('JSONStream');
const stream = fs.createReadStream('large.json')
.pipe(JSONStream.parse('items.*'));
V8 编译优化
JIT 编译流程
JavaScript
JavaScript 代码
↓
解析生成 AST
↓
字节码(解释执行)
↓
热点检测 → TurboFan 优化编译
↓
优化机器码(快速执行)
↓
类型假设失败 → Deoptimization(回退字节码)
保持优化状态
JavaScript
// 避免 Deoptimization
// 1. 保持参数类型一致
function add(a, b) {
return a + b;
}
add(1, 2); // 优化:整数加法
add(1.5, 2); // 可能 Deopt:浮点数
add('1', '2'); // Deopt:字符串
// 2. 避免修改对象结构
const obj = { a: 1, b: 2 };
obj.c = 3; // Deopt:新属性
// 3. 避免数组越界
const arr = [1, 2, 3];
arr[100] = 4; // Deopt:稀疏数组
内联优化触发条件
JavaScript
函数越小越可能内联:
- 函数体 < 600 字符
- 调用次数足够
- 单态调用(参数类型固定)
查看优化状态
text
# 打印优化信息
node --trace-opt app.js
# 打印 Deoptimization
node --trace-deopt app.js
# 详细 JIT 日志
node --trace-jit app.js
缓存策略优化
计算缓存
text
// 烕点:重复计算
function calculate(x, y) {
return Math.sqrt(x * x + y * y); // 每次计算
}
// 优化:Memoization
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = args.join(',');
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const memoizedCalc = memoize(calculate);
LRU 缓存
text
class LRUCache {
constructor(maxSize = 100) {
this.maxSize = maxSize;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return null;
const value = this.cache.get(key);
// 移到末尾(最近使用)
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.maxSize) {
// 删除最旧的(第一个)
this.cache.delete(this.cache.keys().next().value);
}
this.cache.set(key, value);
}
}
算法优化
时间复杂度对比
text
// O(n²) 烕点:嵌套循环
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
if (arr[i] + arr[j] === target) return [i, j];
}
}
// O(n) 优化:哈希表
const map = new Map();
for (let i = 0; i < arr.length; i++) {
const complement = target - arr[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(arr[i], i);
}
查找优化
text
// 烕点:线性查找 O(n)
const found = arr.find(item => item.id === targetId);
// 优化:二分查找 O(log n)(有序数组)
function binarySearch(arr, targetId) {
let left = 0, right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid].id === targetId) return arr[mid];
if (arr[mid].id < targetId) left = mid + 1;
else right = mid - 1;
}
return null;
}
// 优化:Map 查找 O(1)
const map = new Map(arr.map(item => [item.id, item]));
const found = map.get(targetId);
热点优化流程
text
1. 生成火焰图
↓
2. 定位最宽平台(热点)
↓
3. 分析热点代码类型
↓ 循环/对象/字符串/JSON
4. 选择优化策略
↓
5. 验证优化效果(压测对比)
注意:优化前先测量,避免凭直觉优化。优化后必须验证效果。
要点总结
- 使用 clinic.js flame 或 0x 生成火焰图定位热点
- 保持对象属性结构固定,避免 Hidden Class 变化
- 数组保持紧凑、同类型,避免稀疏数组
- 使用 Memoization 缓存计算结果
- 优化算法复杂度,从 O(n²) 到 O(n) 或 O(log n)
📝 发现内容有误?点击此处直接编辑