• 中文
  • ENGLISH
唯快不破,讓nodejs再快一點
2018/01/11

本文作者:六猴

本文首次發表于北斗同構github, 轉載請注明出處

注: 本文為第12屆D2前端技術論壇《打造高可靠與高性能的React同構解決方案》分享內容,已經過數據脫敏處理。

前言

很多前端工程師在做頁面性能調優的過程中,極少關注代碼本身的執行效率,更多關注的是網絡消耗,比如資源合并減少請求數、壓縮降低資源大小、緩存等. 我并不覺得這不合理,相反,在很大程度上這是足夠正確的做法,舉個例子, JS本身的執行時間是30ms(毫秒),在動輒三五秒的頁面加載時間中的占比實在太低了,就算拼了命把性能提升10倍,執行時間降到3ms,整體性能提升也微不足道,甚至在用戶層面都無法感知. 因此去優化其它性能消耗的大頭更加明智.

但從Node.js(服務端)的角度來看,JS本身的執行時間卻變得至關重要,還是之前的例子,如果執行時間從30ms降到3ms, 理論上QPS就能提升10倍,換句話說,以前要10臺服務器才能扛住的流量現在1臺服務器就能扛住,而且響應時間更短.

那到底Node端如何做性能優化呢?

方法

有兩種方法,一種是通過Node/V8自帶的profile能力 , 另一種是通過alinode的 CPU profile功能. 前者只列出了各函數的執行占比, 后者包括更加完整的調用棧,可讀性更強,更加容易定位問題,建議采用后者.

方法1: Node 自帶 profile

  • 第1步: 以–prof參數啟動Node應用
$ node --prof index.js
  • 第2步: 通過壓測工具loadtest向服務施壓
$ loadtest http://127.0.0.1:6001 --rps 10
  • 第3步: 處理生成的log文件
$ node --prof-process isolate-0XXXXXXXXXXX-v8-XXXX.log > profile.txt
  • 第4步: 分析profile.txt文件

profile.txt文件如下圖,包括JS和C++代碼各消耗多少ticks, 具體分析方法詳見node profile文檔

方法2: alinode的CPU profile

  • 第1步: 安裝alinode

alinode是與 Node 社區版完全兼容的二進制運行時環境, 推薦使用tnvm工具進行安裝

$ wget -O- https://raw.githubusercontent.com/aliyun-node/tnvm/master/install.sh | bash

完成安裝后,需要將tnvm添加為命令行程序. 根據平臺的不同,可能是~/.bashrc,~/.profile 或 ~/.zshrc等

$ source ~/.zshrc

以alinode-v3.8.0為例, 對應node-v8.9.0, 下載該版本并啟用它

$ tnvm install alinode-v3.8.0
$ tnvm use alinode-v3.8.0

  • 第2步: 用安裝的alinode運行時啟動應用
$ node --perf-basic-prof-only-functions index.js
  • 第3步: 通過壓測工具loadtest向服務施壓
$ loadtest http://127.0.0.1:6001 --rps 10
  • 第4步: cpu profile

假設啟動的worker進程號為6989, 執行以下腳本, 三分鐘后將在/tmp/目錄下生成一個cpuprofile文件/tmp/cpu-profile-6989-XXX.cpuprofile
腳本詳見take_cpu_profile.sh

$ sh take_cpu_profile.sh 6989
  • 第5步: 將生成的cpuprofile文件導入到Chrome Developer Tools進行分析

cpu profile img

實戰

下面通過一個真實的案例展示如何一步步地做性能調優.

通過loadtest請求1000次,統計平均RT, 初始RT為15.8ms

origin

剔除program和GC消耗,性能消耗的前三位分別是get,J_eval三個方法

展開最耗性能的get方法調用棧,可以定位到get方法所在的位置,具體代碼如下

{
key: 'get',
value: function get(propName) {
if (!this.state[propName]) {
return null;
}
return JSON.parse(JSON.stringify(this.state[propName]));
}
}

方法體中,JSON.parse(JSON.stringify(obj))雖然使用便捷,但卻是CPU密集型操作. 做一次驗證,去除該操作, 直接返回this.state[propName]. RT時間降為12.3ms了

這僅僅是一次試驗,肯定不能直接移除JSON.parse(JSON.stringify(obj)), 不然會影響業務邏輯的. 參考下常用拷貝方法的性能對比, 自配梯子. 截圖如下:

其中性能最優的是lodash deep clone,采用該庫替換,再驗證一遍, RT降為12.8ms

第二耗性能是的J方法,里面大部分是各個組件的render時間,暫時略過,以同樣的方式對_eval方法進行一次優化, RT降為10.1ms.

以此類推,根據CPU profile找出性能消耗的點,逐個去優化.

訂閱我們
体彩20选5开奖结果查询