ARM64_Lab

npm install はキャッシュなし21.77秒、あり3.2秒だった

この記事の見出し
  1. 小さい package.json から始める
  2. 4依存が80パッケージになる
  3. ついでに Node.js のループも見た
  4. ループ回数を揃えていなかった
  5. cold は取得待ち、warm は展開待ち
  6. 消す前に一呼吸置く

npm install はキャッシュなしで21.77秒、同じ条件の2回目は3.2秒という結果。

差は6.8倍ある、というだけなら単純な比較に見えるが、Express、lodash、chalk、axios の4つだけを書いた package.json から80パッケージまで広がる流れを見ると、待っている相手はnpm本体だけではない(次は保存場所を変えて測り直したい)。最初はもっと軽い作業だと思っていた。油断したところ。けれど実際に node_modules を数えると2027ファイル、6058033バイトまで広がっている。見た目は4依存でも、npm が解決して展開するものは4ファイルではなかった。

2026年7月4日に、Surface Pro 11th Edition の ARM64 Windows で測った(warm 側も展開の待ち時間として残る)。記録上の日付は 2026-07-04。Node.js は24.13.0。電源は AC 接続で、電源プランは既定のバランスのままにしている。

小さい package.json から始める

依存は express、lodash、chalk、axios だけにした(小さい package.json という見た目に引っ張られていた)。npm install--no-audit --no-fund を付け、余計な通信を減らしている。キャッシュディレクトリは測定用に分けた。

npm install --cache "...\npmtest\.npmcache" --no-audit --no-fund

cold は npm のキャッシュが空の状態。warm は同じキャッシュを使って、もう一度 node_modules を作る測定にしている。ここで見たいのは、初回のネットワーク待ちと、2回目の展開コストの差で、CPUだけを見るつもりなら、この測り方はかなり混ざりものが多い(それでも普段の待ち時間には近い)。

CPU の速さを見るつもりで始めた測定ではない。npm install はネットワーク、圧縮ファイルの取得、依存解決、細かいファイルの作成が混ざる。特に cold は待ち時間の塊になりやすく、ARM64 か x64 かを語るには向いていない。

4依存が80パッケージになる

インストールの結果はこうなった。

条件 elapsed_s node_modules_files node_modules_bytes npm の表示
cold 21.77秒 2027 6058033バイト added 80 packages in 21s
warm 3.2秒 2027 6058033バイト added 80 packages in 3s

4依存が80パッケージになり、2027ファイルになる。この数字を見た瞬間に、3.2秒の意味が少し変わった。warm は単なる「2回目だから速い」ではなく、キャッシュから取り出したものを大量の小さいファイルとして置き直す時間に近い(CPU よりファイル作成側の匂いが強かった)。

この処理は、CPU の整数演算だけで決まらず、むしろファイルシステムへ小さいファイルを何度も作る側の負荷が見えてくる(node_modules らしい重さ、という感じ)。node_modules を消してから作り直す操作が妙に重く感じるのは、依存の数だけでは説明しきれない(lockfile だけで片づける話ではなかった)。

ついでに Node.js のループも見た

同じ JSON には Node.js のループベンチも入っていた。整数の剰余ループと BigInt 版を、それぞれ3回ずつ走らせている。

ベンチ 1回目 2回目 3回目
node_int_loop 238ms 195ms 196ms
node_bigint 1968.4358ms 1737.8546ms 1811.2391ms

普通の整数ループは195msから238msの範囲に収まった。BigInt 版は1737.8546msから1968.4358msで、桁が違う(小さい package.json という見た目に引っ張られていた)。ぱっと見ると8倍から10倍くらい遅いように見える。

ここで一度止まった。表の見た目だけなら BigInt が重い、で済ませたくなるが、出力値を見直すと話が変わるし、このまま比率だけを書いていたら測定の読み方を間違えるところだった。

ループ回数を揃えていなかった

失敗は、通常版と BigInt 版でループ回数を揃えていなかったこと。出力値を見ると、通常版は結果が599999994、BigInt 版は119999995だった。剰余の足し込みなので、BigInt 版は通常版より少ない回数で回った形になる(node_modules の細かさを実際に見るまで軽く見ていた)。

そのまま実行時間だけを割ると、BigInt の重さを小さく見積もってしまう。ここは1回あたりに直す必要があった。通常版は、238ms、195ms、196ms。BigInt 版は、1968.4358ms、1737.8546ms、1811.2391ms。ループ回数の違いを補正すると、通常版はおおむね1ns台、BigInt 版は40ns台になる。3回の平均で見ると、BigInt は約44倍重い。

最初から回数を揃えるべきだった。これは測った後で気づくまで少し詰まったところ。整数ループの表だけを見て「BigInt は約9倍」と書いていたら、実態よりかなり甘い記事になったはずだ(warm 側も展開の待ち時間として残る)。

cold は取得待ち、warm は展開待ち

cold の21.77秒は、npm のキャッシュが空だったことが大きい(キャッシュの有無だけで説明しきるには粗い)。stdout でも added 80 packages in 21s と出た。ここにはパッケージ取得の待ちが入るので、Snapdragon X Elite の CPU 性能だけを取り出して語るには弱い(次は保存場所を変えて測り直してみたい)。

一方で warm の3.2秒は、自分の手元ではかなり実用に近い数字だった。キャッシュは温かい。それでも2027ファイルを作るために3.2秒かかった。小さいファイルを大量に作る処理は、単純なループよりずっと生活に近い負荷だ(普段の開発で待たされるのは、むしろこちらのほう)。

Node.js の整数ループは速かった。2億回級の処理でも200ms前後で終わる。けれど npm install の待ち時間はそこでは決まらない(なぜ四つの依存でここまで膨らむのか、後で気になった)。ファイルが増え、ディレクトリが増え、ウイルス対策や同期の都合も横から入る。実際に普段の開発で待つのは、こういう混ざった処理のほうではないだろうか。

消す前に一呼吸置く

CI では npm のキャッシュを効かせる価値がある。21.77秒が3.2秒になるなら、短いジョブでも積み上がる。依存が4つだけの小さいプロジェクトでこの差なので、依存が増えたリポジトリでは無視しにくい(CPU よりファイル作成側の匂いが強かった)。

ローカルでも、気軽に node_modules を消す前に一呼吸置くようになった。壊れたときに消すのは楽だが、次に2027ファイルを作り直す時間が来るので、まずキャッシュや lockfile 側を見てみる(なぜ四つの依存でここまで膨らむのか、後で気になった)。キャッシュが残っていれば3.2秒で済むとはいえ、何度もやれば作業のリズムは切れてしまう(lockfile だけで片づける話ではなかった)。

今回の結論は、ARM64 Windows で npm install が動いた、というだけでは少し弱い。むしろ見どころは、4依存が80パッケージ、2027ファイル、6058033バイトに膨らむところだった。次に測るなら、依存数を増やすより、キャッシュ、ウイルス対策、OneDrive 外の作業フォルダでどこまで変わるかを分けて見たい。

a
arm64lab — 個人運営

Surface Pro 11th Edition(Snapdragon X Elite)を2025年5月から常用機にしている個人の記録です。ARM64 版 Windows で詰まったところと、その場で測った値をそのまま書き残しています。特定の企業・団体とは関係がなく、いかなる組織を代表する見解でもありません。