程序完成以后城须,性能和穩(wěn)定性又是我們關(guān)注的焦點(diǎn)风瘦,本文總結(jié)一下工作中遇到的性能測(cè)試方法,當(dāng)然這里是針對(duì)node振峻。
性能分析是比較困難的工作臼疫,如果一個(gè)程序很慢,他可能把時(shí)間花在了CPU扣孟,I/O烫堤,網(wǎng)絡(luò)傳輸,垃圾回收和一些其他事情凤价。如果我們能找到變慢的地方鸽斟,并加以改進(jìn),那么我們的程序?qū)⒈雀?jìng)爭(zhēng)對(duì)手的優(yōu)秀料仗。
下面我們就看看有哪些方法可以幫助我們找到性能不佳的模塊湾盗。
日志
開(kāi)發(fā)app的程序員往往不喜歡使用日志,無(wú)論是iOS立轧,Android或者h(yuǎn)ybrid的開(kāi)發(fā)方案格粪,都有完善的調(diào)試機(jī)制,IDE更是好用的不行氛改,似乎日志只用在服務(wù)器端的程序帐萎,嵌入式和驅(qū)動(dòng)開(kāi)發(fā)中才用的到。不過(guò)這里想介紹一下日志也可以性能調(diào)試胜卤,所以給自己的模塊打上完整的日志疆导,是一種很好的開(kāi)發(fā)方法。
如果要用日志進(jìn)行性能分析葛躏,必須讓日志打印出以下幾個(gè)要素
- 時(shí)間
- 功能模塊
- 描述
- 進(jìn)程(線程)Id
我們看一個(gè)例子澈段。
在下圖中我們可以看到:
- 使用notepad++工具打開(kāi)log悠菜。
- 按照功能模塊搜索。
- 在下面?zhèn)兛梢钥吹竭^(guò)濾后的log败富。這個(gè)和grep的功能很像哦悔醋。
- 通過(guò)一行一行的查看(注意只看你過(guò)濾的模塊),發(fā)現(xiàn)在哪里耗時(shí)最多兽叮。
- 比如發(fā)現(xiàn)某兩行之間耗時(shí)2秒芬骄,超過(guò)預(yù)期,我們可以去看完整log鹦聪,發(fā)現(xiàn)在此之間做了哪些操作账阻。
- 如果是多進(jìn)程的系統(tǒng),也可以根據(jù)進(jìn)程過(guò)濾泽本。
使用log的方法需要你可以修改源碼淘太,在關(guān)心的地方插入log(比如一些生命周期的關(guān)鍵地方,啟動(dòng)結(jié)束與顯示等等)观挎,也需要在團(tuán)隊(duì)中推行這種做法琴儿,不然log不全段化,看了也沒(méi)啥用嘁捷。
如果log非常全,就需要使用過(guò)濾工具去過(guò)濾log显熏,grep和nodepad++的過(guò)濾功能都很強(qiáng)大雄嚣。
工具
性能測(cè)試的工具很多,總體思想都是圖形化調(diào)用堆棧喘蟆,顯示占用的事件百分比缓升。
1. Flame Graphs
下面顯示的Flame Graphs可以顯示出函數(shù)占用的時(shí)間,應(yīng)該說(shuō)是非常好用蕴轨。
具體的使用方法可以查看這篇博客cpuflamegraphs
2. v8-profiler
var fs = require('fs');
var profiler = require('v8-profiler');
profiler.startProfiling('1', true);
var profile1 = profiler.stopProfiling();
profiler.startProfiling('2', true);
var profile2 = profiler.stopProfiling();
console.log(snapshot1.getHeader(), snapshot2.getHeader());
profile1.export(function(error, result) {
fs.writeFileSync('profile1.json', result);
profile1.delete();
});
profile2.export()
.pipe(fs.createWriteStream('profile2.json'))
.on('finish', function() {
profile2.delete();
});
profiler生成的文件可以用 node-inspector查看
** 3.d8 **
d8工具需要自己編譯生成港谊,我們可以看看v8的的代碼,弄清楚v8工具以后橙弱,我們也可以自己寫(xiě)工具去測(cè)試性能歧寺,因?yàn)閷?shí)際上v8本身暴露了這些接口。
如果沒(méi)有d8棘脐,node也有類似的功能斜筐。
node --prof --prof_lazy app.js
我們看一下最終的結(jié)果:
下面可以看到cpu的使用情況,是不是很不錯(cuò)啊蛀缝。
Statistical profiling result from benchmarks\v8.log, (4192 ticks, 0 unaccounted, 0 excluded).
[Shared libraries]:
ticks total nonlib name
9 0.2% 0.0% C:\WINDOWS\system32\ntdll.dll
2 0.0% 0.0% C:\WINDOWS\system32\kernel32.dll
[JavaScript]:
ticks total nonlib name
741 17.7% 17.7% LazyCompile: am3 crypto.js:108
113 2.7% 2.7% LazyCompile: Scheduler.schedule richards.js:188
103 2.5% 2.5% LazyCompile: rewrite_nboyer earley-boyer.js:3604
103 2.5% 2.5% LazyCompile: TaskControlBlock.run richards.js:324
96 2.3% 2.3% Builtin: JSConstructCall
...
[C++]:
ticks total nonlib name
94 2.2% 2.2% v8::internal::ScavengeVisitor::VisitPointers
33 0.8% 0.8% v8::internal::SweepSpace
32 0.8% 0.8% v8::internal::Heap::MigrateObject
30 0.7% 0.7% v8::internal::Heap::AllocateArgumentsObject
...
[GC]:
ticks total nonlib name
458 10.9%
[Bottom up (heavy) profile]:
Note: percentage shows a share of a particular caller in the total
amount of its parent calls.
Callers occupying less than 2.0% are not shown.
ticks parent name
741 17.7% LazyCompile: am3 crypto.js:108
449 60.6% LazyCompile: montReduce crypto.js:583
393 87.5% LazyCompile: montSqrTo crypto.js:603
212 53.9% LazyCompile: bnpExp crypto.js:621
212 100.0% LazyCompile: bnModPowInt crypto.js:634
212 100.0% LazyCompile: RSADoPublic crypto.js:1521
181 46.1% LazyCompile: bnModPow crypto.js:1098
181 100.0% LazyCompile: RSADoPrivate crypto.js:1628
性能測(cè)試工具很多顷链,有了工具以后還需要我們對(duì)需要優(yōu)化的代碼比較熟悉,仔細(xì)檢查屈梁,才能找到有可能的幾個(gè)點(diǎn)嗤练,慢慢優(yōu)化榛了,可以說(shuō)是一件很費(fèi)時(shí)間的工作。
自己寫(xiě)測(cè)試工具
上面的工具雖然好用煞抬,但是如果我們自己能寫(xiě)得化豈不是更好忽冻,所以我們看看能不能自己寫(xiě)一個(gè)。
我們先看看v8-profiler是如何做的
https://github.com/node-inspector/v8-profiler/blob/master/src/cpu_profiler.cc
v8::CpuProfiler::StartProfiling(title);
好像v8自己有性能分析的模塊哦此疹。
我們?cè)俅蜷_(kāi)v8代碼僧诚。可以看到
所以仿照現(xiàn)有的工具蝗碎,我們完全可以打造出自己的工具阿湖笨。