Node.jsのパフォーマンスチューニングのtips
- --inspect, --inspect-brk
- --trace-opt, --trace-deopt
- --prof
- --trace-events-enabled
- --trace-gc
- node-report
- Performance Timing API
- 優しいコードの書き方へ
- v8::SnapshotCreator
- さいごに
Node9が10/31に出ました🎉🎉🎉
Node v9.0.0 (Current) | Node.js
今回はNode単体の話なので、Express、Nginx等のチューニングに関してはココには書きません。
また、libuv等のコード内部の話もしません。
--inspect, --inspect-brk
もともとあった、--debug
から移行されました。(v8.0.0 ~)
Chromeを使いデバッグ、プロファイリング等を使えるようになります。
ブラウザで使えるので、いつも使っている感じと同じです。
--inspect-brk
は--debug-brk
と同様に最初の行にブレークポイントを設置し、起動します。
$ node --inspect test.js Debugger listening on ws://127.0.0.1:9229/b565921e-23f2-4cee-b124-33e97fc3aa32 For help see https://nodejs.org/en/docs/inspector
chromeからchrome://inspect/#devices
を指定すると以下のように選択肢がでるので、そこからinspectを選ぶと起動します。
インスペクターのクライアント一覧: Debugging - Getting Started | Node.js
Inspector Help | Node.js
個人的には、NiMを入れると楽かなーと思います。
abouthiroppy.github.io
--trace-opt, --trace-deopt
コードの最適化の解析を行います。
$ node --trace-opt test.js [marking 0x3ad0f4375d1 <JSFunction normalizeStringPosix (sfi = 0x3ad423d7d81)> for optimized recompilation, reason: hot and stable, ICs with typeinfo: 46/67 (68%), generic ICs: 0/67 (0%)] [compiling method 0x3ad0f4375d1 <JSFunction normalizeStringPosix (sfi = 0x3ad423d7d81)> using TurboFan] [optimizing 0x3ad0f4375d1 <JSFunction normalizeStringPosix (sfi = 0x3ad423d7d81)> - took 1.867, 1.776, 0.019 ms] [completed optimizing 0x3ad0f4375d1 <JSFunction normalizeStringPosix (sfi = 0x3ad423d7d81)>] [marking 0x3ad423fcee1 <JSFunction Module._nodeModulePaths (sfi = 0x3ad423ba5a1)> for optimized recompilation, reason: hot and stable, ICs with typeinfo: 23/23 (100%), generic ICs: 0/23 (0%)] [compiling method 0x3ad423fcee1 <JSFunction Module._nodeModulePaths (sfi = 0x3ad423ba5a1)> using TurboFan] [optimizing 0x3ad423fcee1 <JSFunction Module._nodeModulePaths (sfi = 0x3ad423ba5a1)> - took 0.659, 3.009, 0.049 ms] [completed optimizing 0x3ad423fcee1 <JSFunction Module._nodeModulePaths (sfi = 0x3ad423ba5a1)>]
markingは再コンパイル用のマーキングで、それは再コンパイルされ最適化されます。
最適化が不可能な場合は、マーキングの代わりにdisabled optimization
というのが付きます。
上記をみればわかるように、その関数が最適化されたかどうかがわかります。
--prof
CPUプロファイリングです。 V8内のプロファイラの実行をサンプリングします。
$ node --prof test.js $ ls isolate-0x103000000-v8.log test.js $ node --prof-process isolate-0x103000000-v8.log # logは読みづらいので読めるようにする
各セクションごとに情報が分かれます。
[Summary]: ticks total nonlib name 3 5.0% 5.0% JavaScript 50 83.3% 83.3% C++ 1 1.7% 1.7% GC 0 0.0% Shared libraries 7 11.7% Unaccounted
取得されたサンプルの比率(5.0%, 83.3%, etc...)が割合となり、その言語のコードで発生したことを示します。
そして、各セクションを見るといいと思います。
セクション例
ticks parent name 6326 44.2% /lib/x86_64-linux-gnu/libm-2.15.so 6325 100.0% LazyCompile: *exp native math.js:91 6314 99.8% LazyCompile: *calculateMandelbrot http://localhost:8080/Demo.js:215
各セクションは、ツリーになっており、この場合は親コールの合計時間における44.2%がシステム内のmath.exp()
を実行するのに使われています。
関数名の前の*
はその時間が最適化された関数で費やされていることを示し、~
の場合は最適化された関数ではないことを示します。
詳しくは公式が出している以下の記事を見ると、手順がわかりやすいと思います。
Easy profiling for Node.js Applications | Node.js
--trace-events-enabled
トレース情報を管理します。
--trace-events-enabled
フラグを渡すと有効化されます。
カテゴリを指定したい場合は、--trace-event-categories
を使い続けてカテゴリを指定します。
カテゴリデフォルトはnode
とv8
になります。
chromeでchrome://tracing/
を指定することにより、生成したのをロードすることが可能です。
$ node --trace-events-enabled test.js $ node --trace-events-enabled --trace-event-categories v8,custom-category test.js
Tracing | Node.js v9.0.0 Documentation github.com www.chromium.org
--trace-gc
Garbage Collectionのトレースです。
メモリリークのデバッグに役立つでしょう。
$ node --trace-gc test.js [43929:0x102801c00] 39 ms: Scavenge 3.4 (6.3) -> 3.1 (7.3) MB, 0.9 / 0.0 ms allocation failure [43929:0x102801c00] 50 ms: Scavenge 3.6 (7.3) -> 3.5 (8.3) MB, 1.2 / 0.0 ms allocation failure $ node --optimize_for_size --max_old_space_size=4096 --gc_interval=100 #このようにV8のGCを操作することも可能
--expose-gc
を指定することにより、手動でGCを起こすことも可能です。
メモリリーク周りは以下の記事を参考にするとわかりやすくていいと思います。
実例
node-report
公式が出しているモジュールです。
現在、node-reportはCoreとは別で切り分けられておりスタンドアローンですが、将来的にはCoreに入る予定です。
ネイティブのスタックトレース、ヒープ統計情報、プラットフォーム情報、リソース使用状況などが人間が読める形でレポート化されます。
$ npm i node-report $ node -r node-report test.js $ cat node-report.20171105.202142.9066.001.txt ================================================================================ ==== Node Report =============================================================== ... Node.js version: v9.0.0 ... ================================================================================ ==== JavaScript Stack Trace ==================================================== ... ================================================================================ ==== Native Stack Trace ======================================================== ... ================================================================================ ==== JavaScript Heap and Garbage Collector ===================================== ... ================================================================================ ==== Resource Usage ============================================================ ... ================================================================================ ==== Node.js libuv Handle Summary ============================================== ... ================================================================================ ==== System Information ======================================================== ... ================================================================================
Performance Timing API
v8.5.0から入ったブラウザでも使われるAPIです。
現在はStability: 1(実験的)です。
Performance Timing API | Node.js v8.9.0 Documentation
詳しくは以下の記事をどうぞ
優しいコードの書き方へ
v8.3.0からV8のTurbofan, IgnitionがデフォルトでCrankshaftから移行され、昔のような最適化のためのコードの書き方をしなくても良くなりました。
先日のChrome Dev SummitでもV8チームが今後はそのようなアンチパターンをなくしていくと言っています。(つまりどの書き方をしても同じ結果になる)
また、トランスパイルは気をつけるべきです。
Babelにはbabel-preset-envというターゲットバージョンによりトランスパイルをするツールがあります。
babel-preset-envではNodeのバージョンを指定することにより、V8に優しいコードに変換することが可能です。
すべてのコードをトランスパイルするべきではありません。
基本的にトランスパイルされるコードは無駄な処理が多いからです。(これはそのものがエンジン側で実装されてないため)
なので、エンジン側で未実装なもの(e.g. stage-x)だけをトランスパイルするべきです。
{ "presets": [ ["@babel/env", { "targets": { "node": "current" } }] ] }
先日、monorepoのbabelへ移行され次のバージョンではscoped packagesになりました🎉
v8::SnapshotCreator
将来的に入るかもしれませんが、今現在、ArrayBuffers
に関して議論中です。
さいごに
今回は、パフォーマンスチューニングをするのに手助けになる手法を数個列挙してみました。
しかし、Node, V8の最適化周りのオプションの話をするとまだたくさんあるとおもいますが一旦このへんで。
詳しくはnode --v8-options
へ。--print-code
, --print-opt-code
, --code-comments
--track-heap-objects
, etc...
その他には、I/O(libuv)とイベントループの理解も大切だと思います。
もしチューニング等でお困りでしたら、Twitterかメールで聞いてくだされば答えれるかもしれません。
また、今月Node学園祭があるので是非お越し下さい:)