这里讲的是如何利用Chrome浏览器来debug node程序,并利用Profiler来做性能分析。
Chorme Devtools不仅仅能做网页调试,还可以调试node程序,借助此工具,可以查看调用栈信息,燃尽图,内存快照等,辅助我们对程序进行优化。
使用步骤
下面是步骤
启动server
node --inspect-brk index.js
打开chrome,输入chrome://inspect
- node —inspect-brk index.js // 这个是暂停调试模式
- 打开chrome,输入chrome://inspect
- 继续执行代码后,点击Profile,然后开始生成快照
- ab工具进行压测,完了结束快照;
这样我们就能分析调用栈,燃尽图等;
实例:根据结果优化程序
默认Heavy(Bottom up)
选择Chart
这个结果显示 readFileSync占用了很大的性能,怀疑可能每一次请求都在调用readFileSync,那么考虑是不是要提取出来。
1. 优化这readFileSync
查看代码:
app.use(
mount('/', async (ctx) => {
ctx.body = fs.readFileSync(__dirname + '/source/index.htm', 'utf-8')
})
);
这样写每一次请求之后都会去readFile,所以把它抽出来:
const html = fs.readFileSync(__dirname + '/source/index.htm', 'utf-8')
app.use(
mount('/', async (ctx) => {
ctx.body = html
})
);
在进行ab测试,修改之后qps已经提升到400多,以前是200多,翻了一倍。
2. 优化byteLengthUtf8
还是利用Chrom Dev tools,发现:
- 之前的最大性能占比(readFileSync)已经不见了,优化成功;
- 取而代之的新的占比最高是这个:byteLengthUtf8
思考:
为什么会有这个byteLengthUFT8(这个是获取UTF8的长度),这里有个“经验活儿”就是:
node接口里面的body 比如 ctx.body = stringBody这个stringBody是string的话,是要默认变成buffer的(底层是c++),所以这里涉及一个转换;
所以优化的思路就是直接用buffer来输出:
修改前:
const html = fs.readFileSync(__dirname + '/source/index.htm', 'utf-8')
修改后:
// 不指定utf8
const htmlBuf = fs.readFileSync(__dirname + '/source/index.htm')
app.use(
mount('/', async (ctx) => {
ctx.type = 'html' // buffer就需要指定是html了,否则可能就当成要下载的文件了
ctx.status = 200
ctx.body = htmlBuf
})
);
改完之后再来ab测试,效果:qps直接变成900多了,很大的提升!
程序性能优化的准则
- 减少不必要的计算
- 空间换时间
所谓“不必要的计算”,最重要的思考思考:在用户能感知到的时间里,这个计算是不是必要的?
- 如果是不必要的,是不是可以提前将计算结果缓存
- 比如上面例子中,把服务阶段的活儿安排到启动阶段;
快照:检测内存泄漏
类似上文中,利用chrome dev tools,思路就是内存快照:
启动server进行ab测试。
测试之前照一个快照, 测试之后再照一个快照。理论上没有内存泄漏的情况下,两次快照得到的内存占用大小应该差不多。但是如果发现有较大差别,说明存在内存泄漏!
举个例子:
伪造一个内存泄漏:const leak = []
app.use(
mount('/', async (ctx) => {
ctx.body = html
leak.push(html)
})
);
对比前后快照,可以看到,压测前后内存差了很多:
可以看到:
- 启动ab测试前,快照内存623MB
- 结束ab测试之后,快照内存1728MB
典型的内存侧漏了~
dev tool 可以选择「对比」能很清晰的看到,下图所示,分配了多少内存,释放了多少内存:
上图显示,分配了1159 169….释放了468 856….
点击一个string可以在下面看到,这个内存的持有者就是leak变量,就是刚才伪造的内存泄漏数组。