🔄

npm, yarn, pnpmパッケージマネージャをベンチマークしてみた

2023/08/30に公開
5

3行まとめ

  • pnpmが速いとのことなのでベンチマークをしてみましたが、yarn v4が最速です。
  • yarn v1はキャッシュありのときにとても遅いでが、最新のv4は全項目において最速です。
  • Node.jsのdocker imageでデフォルトになっているのはyarn v1なのでご注意ください。

概要

  • pnpmがどのくらい速いかを計測しています。
  • Next.jsやReactJSを使ったときのベンチマークは掲載されていますが、実際自分のプロジェクトではどの程度かわるのかをベンチマークしてみたいと思います。
  • pnpmとは何かを知りたい場合はpnpm概要を参照するのが良いです。

実験環境

個人サービスである銀行コード検索APIのpackage.jsonを使います。

package.jsonの中身は以下のようになっています。

{
  "packageManager": "yarn@1.22.15",
  "private": true,
  "dependencies": {
    "@hotwired/turbo-rails": "^7.3.0",
    "@popperjs/core": "^2",
    "@rails/activestorage": "^7.0",
    "@rails/ujs": "^7.0",
    "bootstrap": "^5.3",
    "bootstrap-icons": "^1.10.5",
    "bootswatch": "^5.3",
    "esbuild": "^0.18.17",
    "jquery": "^3.7"
  },
  "devDependencies": {
    "eslint": "^8.46.0",
    "eslint-plugin-react": "^7.33.1",
    "npm-check-updates": "^16.10.17"
  }
}

測定環境

root@1bf5d376c3a0:/app# node -v
v18.17.0
root@1bf5d376c3a0:/app# npm -v
9.8.1

npmのbenchmark

まずは基本となる npm を計測します。28秒。

root@1bf5d376c3a0:/app# rm -rf node_modules/ package-lock.json
root@1bf5d376c3a0:/app# time npm install

added 383 packages, and audited 384 packages in 28s

80 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

real	0m28.308s
user	0m9.264s
sys	0m5.575s

yarn v1のbenchmark

Node.jsに標準で組み込まれたyarnは、13秒。

root@1bf5d376c3a0:/app# rm -rf node_modules/ package-lock.json
root@1bf5d376c3a0:/app# time yarn install
yarn install v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 13.16s.

real	0m13.362s
user	0m5.338s
sys	0m6.641s/

yarn v4のbenchmark

最新のyarnで計測します。なんと5.8秒!
計測ミスかと思って再度docker runして試しましたが同じ結果です。

root@1bf5d376c3a0:/# mkdir /app
root@1bf5d376c3a0:/# cat > package.json
root@1bf5d376c3a0:/app# yarn set version berry
root@1bf5d376c3a0:/app# yarn -v
4.0.2
root@1bf5d376c3a0:/app# time yarn install
➤ YN0000: · Yarn 4.0.2
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @hotwired/turbo-rails@npm:7.3.0, @popperjs/core@npm:2.11.8, and 474 more.
➤ YN0000: └ Completed in 3s 435ms
➤ YN0000: ┌ Fetch step
➤ YN0013: │ 455 packages were added to the project (+ 84.83 MiB).
➤ YN0000: └ Completed in 1s 442ms
➤ YN0000: ┌ Link step
➤ YN0000: │ ESM support for PnP uses the experimental loader API and is therefore experimental
➤ YN0007: │ esbuild@npm:0.18.20 must be built because it never has been before or the last one failed
➤ YN0000: └ Completed in 0s 501ms
➤ YN0000: · Done with warnings in 5s 438ms

real	0m5.816s
user	0m7.101s
sys	0m1.844s

pnpmのbenchmark

今回注目のpnpmは18秒。

root@1bf5d376c3a0:/app# time pnpm i
Packages: +347
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 368, reused 0, downloaded 347, added 347, done
node_modules/.pnpm/esbuild@0.19.2/node_modules/esbuild: Running postinstall script, done in 93ms

dependencies:
+ @popperjs/core 2.11.8
+ @rails/activestorage 7.0.7-2
+ @rails/ujs 7.0.7-2
+ bootstrap-icons 1.10.5
+ bootswatch 5.3.1
+ esbuild 0.19.2
+ jquery 3.7.1

devDependencies:
+ eslint 8.48.0
+ npm-check-updates 16.13.2

Done in 17.9s

real	0m18.139s
user	0m7.202s
sys	0m5.991sx

余談: pnpmのエラー

余談ですが、2回目以降はエラーが出力されて計測できませんでした。

root@1bf5d376c3a0:/app# rm -rf node_modules/ .pnpm-store/
root@1bf5d376c3a0:/app# time pnpm install
Packages: +438
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ERR_PNPM_LINKING_FAILED  Error: ENOENT: no such file or directory, copyfile '/app/.pnpm-store/v3/files/cf/83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e' -> '/app/node_modules/.pnpm/resolve@2.0.0-next.4/node_modules/resolve_tmp_124/test/resolver/same_names/foo.js'
Progress: resolved 459, reused 0, downloaded 438, added 197

real	0m20.723s
user	0m9.377s
sys	0m6.953s

追記: package-lock.json を削除したら成功しました。また、コンテナを立ち上げ直したら成功したりしたのでpnpmが内部で使っているハードリンク関連とDocker for macOSの相性が悪そうです。

キャッシュ無しベンチマーク結果

Package manager Result (s)
npm 28.308
yarn v1 13.362
yarn v4 5.816
pnpm 18.139

考察

  • yarn v4がとても速いです。nodeのdocker imageのデフォルトではv1が使われてしまうので明示的にv4を設定する必要があります。
  • 今回は初回実行を計測しました。
  • pnpmの初回は遅いですが、パッケージがシステム上で共有されること、依存関係の解決が高速なことを考えると2回目の実行はそこそこ速いです。
    • 実際の開発では、CIや開発環境でパッケージの差分を追加していくと行った差分での利用が多いと思います。その際には高速に動作するので実運用では利点がありそうです。

次にパッケージマネージャを通して削除、追加する際のベンチマークを計測します。

パッケージの削除、追加の計測

実際の運用時に多いユースケースであるパッケージの追加、削除のときのベンチマークを計測してみます。

bootstrap パッケージを削除して、追加するケースを想定してベンチマークを取ってみます。

npm

トータル1.5秒。速いです。

root@7d57aaa10c85:/app/a# time npm uninstall bootstrap

removed 1 package, and audited 383 packages in 686ms

80 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

real	0m0.863s
user	0m0.772s
sys	0m0.160s
root@7d57aaa10c85:/app/a# time npm install bootstrap

added 1 package, and audited 384 packages in 767ms

81 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

real	0m0.943s
user	0m0.858s
sys	0m0.231s

yarn v1

トータル38秒。めちゃ遅いです。

root@7d57aaa10c85:/app/a# time yarn remove bootstrap
yarn remove v1.22.19
[1/2] Removing module bootstrap...
[2/2] Regenerating lockfile and installing missing dependencies...
success Uninstalled packages.
Done in 17.74s.

real	0m18.013s
user	0m5.547s
sys	0m8.845s
root@7d57aaa10c85:/app/a# time yarn add bootstrap
yarn add v1.22.19
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ bootstrap@5.3.1
info All dependencies
└─ bootstrap@5.3.1
Done in 20.55s.

real	0m20.919s
user	0m5.861s
sys	0m9.193s

yarn v4

addとremoveがそれぞれ1秒以内に終わります。速い!

root@0c8caa909f49:/app# time yarn remove bootstrap
➤ YN0000: · Yarn 4.0.2
➤ YN0000: ┌ Resolution step
➤ YN0085: │ - bootstrap@npm:5.3.2
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: │ ESM support for PnP uses the experimental loader API and is therefore experimental
➤ YN0000: └ Completed
➤ YN0000: · Done with warnings in 0s 362ms

real	0m0.848s
user	0m0.778s
sys	0m0.375s
root@0c8caa909f49:/app# time yarn add  bootstrap
➤ YN0000: · Yarn 4.0.2
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + bootstrap@npm:5.3.2
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: │ ESM support for PnP uses the experimental loader API and is therefore experimental
➤ YN0000: └ Completed
➤ YN0000: · Done with warnings in 0s 330ms

real	0m0.791s
user	0m0.820s
sys	0m0.171s

pnpm

トータル4.7秒。速いです。

root@7d57aaa10c85:/app# time pnpm remove bootstrap
Packages: -1
-
Progress: resolved 367, reused 346, downloaded 0, added 0, done

dependencies:
- bootstrap 5.3.1

Done in 2.2s

real	0m2.311s
user	0m2.041s
sys	0m0.906s
root@7d57aaa10c85:/app# time pnpm add bootstrap
Packages: +1
+
Progress: resolved 368, reused 347, downloaded 0, added 0, done

dependencies:
+ bootstrap 5.3.1

Done in 2.1s

real	0m2.239s
user	0m2.205s
sys	0m0.879s

remove/addのベンチマーク結果

Package manager Remove Result (s) Add Result (s)
npm 0.863 0.943
yarn v1 18.013 20.919
yarn v4 0.8480 0.791
pnpm 2.311 2.239

考察

pnpmが公表しているベンチマーク結果とは違いましたやはり手元でやってみるのは意味があります。

Alt text

jsbundling-rails はyarnに依存しているので要注意です。

参考資料

株式会社マインディア テックブログ

Discussion

standard softwarestandard software

npm よりも yarn を使うべきですか? - Quora
https://jp.quora.com/npm-yori-mo-yarn-wo-tsukau-be-ki-desu-ka

プロジェクトでyarnとnpmのどちらを使うかの判断基準は何ですか? - Quora
https://jp.quora.com/プロジェクトでyarnとnpmのどちらを使うかの判断基準は何

数年後の先を見通した技術選定って、結構、難しいですよね。

Matsukura YukiMatsukura Yuki

ですね。

JavaScriptのパッケージマネージャの情勢がすぐ変わるので難しいです。幸い、パッケージマネージャは乗り換えが楽なのが救いです。

手元では、jsbundle-railsがyarnに依存しているのでyarnを使う必要があります。
yarn --watch を内部で使っているようです。これはnpm標準で代用できないので。

kaorun343kaorun343

この記事を初めて公開したときのYarnの最新版はv3だったのですが、v1で検証してるのは何か理由があるのでしょうか👀

Matsukura YukiMatsukura Yuki

nodeに付属しているyarnが1系だからそのまま使っちゃってました。

% docker run --rm -it node:18 bash
root@4c625636da82:/# corepack enable yarn
root@4c625636da82:/# yarn -v
1.22.21

v3でも計測してみます。と思いましたが、v4が出ているのでv4で計測します。→しました。記事を更新しました!