14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Node.jsの実行速度が2.3倍になるという"module.enableCompileCache()"について調べてみた

Posted at

Node.js 22にmodule.enableCompileCache()というAPIが導入されたらしく、これを利用したTypeScriptコンパイラーでは2.3倍、ESLintでは1.3倍の速度改善がなされたと聞いて気になって調べてみました。

module.enableCompileCache()とは?

Node.jsのモジュール(JavaScriptファイル)をコンパイルした結果をディスク上にキャッシュし、2回目以降の実行を高速化する機能です。

import { enableCompileCache } from 'node:module';

// キャッシュを有効化
enableCompileCache();

// この後に読み込まれるモジュールは全てキャッシュの対象に
import './other.js';

仕組み:V8のコード・キャッシング

Node.jsがJavaScriptファイルを実行する際の流れは通常、

ソースコード → パース → バイトコードにコンパイル → 実行

のようになるのですが、キャッシュを使った場合、

1回目:
ソースコード → パース → バイトコードにコンパイル → 実行

2回目以降:
バイトコードのキャッシュを読み込む → 実行

となり、パースとコンパイルの処理を省略できるため高速化されるわけです。

これを裏で支えているのがV8のコード・キャッシングです:

実際の効果

Node.jsのプルリクエストには実際の効果について報告されていました。

TypeScriptコンパイラーに導入したときの効果:

  • キャッシュなし: Time: 120.5 ms ± 1.7 ms
  • キャッシュあり: Time: 51.8 ms ± 1.0 ms

→ 2.33倍の高速化

ESLintに導入したときの効果:

  • キャッシュなし: Time: 129.9 ms ± 7.8 ms
  • キャッシュあり: Time: 105.5 ms ± 1.8 ms

→ 1.23倍の高速化

具体的な使い方

// エントリーポイント(例:bin/cli.js)で1回だけ呼び出し
import { enableCompileCache } from 'node:module';

// 環境変数でキャッシュが無効化されていない場合のみ有効化
if (process.env.NODE_DISABLE_COMPILE_CACHE !== '1') {
  enableCompileCache();
}

// 以降の全てのモジュールがキャッシュ対象に
import { main } from '../src/index.js';
main();

キャッシュの場所

デフォルトではos.tmpdir()/node-compile-cacheに保存されます。変更したい場合は:

  1. 環境変数で指定:
export NODE_COMPILE_CACHE=/path/to/cache
  1. API呼び出し時に指定:
enableCompileCache('/path/to/cache');

キャッシュの無効化

export NODE_DISABLE_COMPILE_CACHE=1

効果が高いケース・低いケース

効果が高い:

  • CLIツール(TypeScript, ESLint等)
    • 毎回nodeコマンドで起動するため、2回目以降が高速化
  • 同じコードを何度も実行する場合
  • 大きなライブラリを使用する場合

効果が低い:

  • Webサーバーのように常駐するプロセス
    • 1回起動したら動き続けるため、キャッシュの恩恵が少ない
  • コードが頻繁に変更される場合
  • 実行時に動的に生成されるコード

注意点

  1. Node.jsのバージョン間でキャッシュは共有されません
  2. テストカバレッジを取る際は、精度が低下する可能性があるため無効化を推奨
  3. ワーカースレッドで使用する場合は、各ワーカーで個別に有効化するか環境変数を設定する必要があります

Dockerでの利用

バイトコードキャッシュをコンテナビルド時に作っておけば、Webサーバーにも応用できるかもしれません。

単純なDockerでの再利用は以下の理由で難しいですが:

  • キャッシュはNode.jsのバージョンに紐付く
  • デフォルトでは一時ディレクトリに保存
  • キャッシュ内のパスが絶対パス

以下のような工夫で、もしかしたら事前にキャッシュを用意できるかもしれません:

# キャッシュを事前生成するステージ
FROM node:22 AS builder
COPY . .
RUN NODE_COMPILE_CACHE=/app/cache node your-script.js

# 実行用ステージ
FROM node:22
COPY --from=builder /app/cache /app/cache
ENV NODE_COMPILE_CACHE=/app/cache

ここは全くの未検証ですが、もしこれが可能なら、Kubernetesのようにコンテナーを自動スケールアウトするような環境や、たまにしかアクセスのないアプリをscale-to-zeroしつつ、瞬時に起動するといったことに繋がりそうで夢があります。

まとめ

module.enableCompileCache()は、特にCLIツールなど、Node.jsを頻繁に起動するアプリケーションで大きな効果を発揮します。エントリーポイントで1行追加するだけで有効になる手軽さと、最大2.3倍という高速化効果は、検討する価値が十分にあると言えるでしょう。

14
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?