この記事は、DMMグループAdvent Calendar 2022 9日目の記事になります。
こんにちは。技術支援チームの四戸義龍と申します。
普段は負債脱却支援などのための社内全般で使うツールの開発・保守や、社内用の認証・認可基盤に関する業務を行っています。
今回は今年の夏頃に話題になっていたBun.shを、実際のプロダクトに置き換えて見た際にどれだけ高速になるのか検証した結果を記事にしました。
そもそもBun.shとは?
Node.jsやDenoなどと同じ様なJavascriptランタイムです。
GitHub - oven-sh/bun: Incredibly fast JavaScript runtime, bundler, transpiler and package manager – all in one.
Node.jsやDenoではV8というJavascriptエンジンを使用しているが、Bun.shではSafariなどで利用されているJavaScriptCoreを採用しており、開発はZigという言語を使用しています。
Zigはオープンソースのプログラミング言語であり、C言語よりも型安全性などが高く、またデフォルトではメモリ管理できずオプションでヒープ利用など、Rustなどに近い印象がありますが、Rustほど複雑ではありません。
また、Wasmを小さくできる点と、 ビルドの標準のオプションでWasmが吐ける点がおそらく採用された理由ではと思っています。
すでにC++、D、Rustがあるのに、なぜZigなのか? ⚡ Zig Programming Language
また、Bun.shはnpmもサポートしており、環境移行性も高いです。
(Bun.shに後押しをされたのか、Denoもnpmサポートをする様になりました)
TypeScriptファイルも自動でトランスパイルしてくれるため、TypeScriptを使う場合にも便利です。
ただし、課題もあります。
現状はNode APIには一部しか対応しておらず、Denoほどの互換性もありません。
まだ出たてなので、不具合も多くbug報告もかなりあります。
また、Bun.shは700万ドル程の資金調達は完了したものの、現在人材募集中で運営もまだ不安定です。
プロダクトに反映する場合は細心の注意を払って利用する様にしてください。
環境について
環境については下記のとおりです。
| 項目 | version |
|---|---|
| 機器 | MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports) |
| プロセッサ | 1.4 GHz クアッドコアIntel Core i5 |
| メモリ | 16 GB 2133 MHz LPDDR3 |
| Node | 18.10.0 |
| Bun.sh | 0.2.2 |
実行するプロダクトは結構古いプロダクトなのでバージョンなど使っている技術も古いです。
| 項目 | version |
|---|---|
| Typescript | 3.9.10 |
| Vue | 2.6.10 |
モジュールのインストール速度
公式サイトとBun.shのGithubを確認するとインストールも最適化されているらしく非常に早いらしいので、こちらから検証していきます。
Replace yarn with bun install and get 20x faster package installs.
公式が言うには20倍早いらしい。
npm install
約 50.8sec
time npm install
/** 中略 **/
added 1692 packages, and audited 1693 packages in 50s
130 vulnerabilities (14 low, 37 moderate, 53 high, 26 critical)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
npm notice
npm notice New major version of npm available! 8.19.2 -> 9.1.3
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.1.3
npm notice Run npm install -g npm@9.1.3 to update!
npm notice
npm install 32.04s user 16.24s system 95% cpu 50.818 total
Bun.sh
約 8.7秒
time bun install bun install v0.2.2 + @types/jest@23.3.14 + @types/uuid@3.4.4 + @vue/cli-plugin-babel@3.12.1 + @vue/cli-plugin-e2e-nightwatch@3.12.1 + @vue/cli-plugin-eslint@3.12.1 + @vue/cli-plugin-typescript@3.12.1 + @vue/cli-plugin-unit-jest@3.12.1 + @vue/cli-service@3.12.1 + @vue/eslint-config-standard@4.0.0 + @vue/eslint-config-typescript@4.0.0 + @vue/test-utils@1.0.0-beta.29 + babel-core@7.0.0-bridge.0 + babel-eslint@10.1.0 + eslint@5.16.0 + eslint-plugin-vue@5.2.3 + ts-jest@23.10.5 + typescript@3.9.10 + vue-template-compiler@2.7.14 + axios@0.18.1 + bootstrap-vue@2.0.0-rc.28 + core-js@2.6.12 + inversify@5.0.5 + reflect-metadata@0.1.13 + uuid@3.3.3 + vue@2.7.14 + vue-class-component@7.2.6 + vue-multiselect@2.1.6 + vue-property-decorator@8.5.1 + vue-router@3.6.5 + vuex@3.1.1 + vuex-typex@3.1.5 1544 packages installed [8.63s] bun install 1.53s user 10.49s system 137% cpu 8.721 total
約5.8倍 の結果になりました。
非常に高速な結果になりましたが、これはnpmのバージョンが古いため速度差が非常に大きくなっている可能性もあります。
流石に20倍ほど早くはありませんでしたが結構早いです。
起動速度
開発環境の立ち上げ速度を計測します。
vue-cli-service serve をpackage.jsonに記載しています。
bunの実行は bun run xxxxで実行可能です。
bunはnpm互換性があって使いやすいです。
Replace npm run with bun run and save 160ms on every run.
Bun runs package.json scripts 30x faster than npm run
Bun — fast all-in-one JavaScript runtime
公式が言うには30倍早いらしいです。
npm
約4.8sec
npm run serve
> dsearch@0.1.0 serve
> vue-cli-service serve
INFO Starting development server...
Starting type checking service...
Using 1 worker with 2048MB memory limit
13% building 30/33 modules 3 active /Users/shinohe-yoshi/d-search/client/dsearch/node_modules/querystring-es3/decode.js=============
/** 中略 **/
98% after emitting CopyPlugin
Version: typescript 3.9.10
Time: 4768ms
App running at:
- Local: http://localhost:1024/
- Network: http://192.168.111.100:1024/
Note that the development build is not optimized.
To create a production build, run npm run build.
Bun.sh
約3.4sec
bun run serve $ vue-cli-service serve INFO Starting development server... Starting type checking service... Using 1 worker with 2048MB memory limit 40% building 208/220 modules 12 active ...modules/bootstrap-vue/es/utils/array.jsBrowserslist: caniuse-lite is outdated. Please run next command `npm update` 98% after emitting CopyPlugin /** 中略 **/ Version: typescript 3.4.5 Time: 3443ms App running at: - Local: http://localhost:1024/ - Network: http://192.168.111.100:1024/ Note that the development build is not optimized. To create a production build, run npm run build.
1秒ばかり早かったですが、30倍の結果が出て欲しいところでしたが、誤差と言っても良いレベルですね。 もう少しmoduleが大量にある場合は差が結構出るかもしれません。
ビルド速度
npm run build
$time npm run build > dsearch@0.1.0 build > vue-cli-service build ⠙ Building for production...Starting type checking service... Using 1 worker with 2048MB memory limit ⠹ Building for production... /** 中略 **/ npm run build 10.00s user 0.95s system 155% cpu 7.024 total
Bun.sh run build
$ time bun run build $ vue-cli-service build ⠙ Building for production...Starting type checking service... Using 1 worker with 2048MB memory limit ⠹ Building for production... /** 中略 **/ bun run build 9.62s user 0.85s system 156% cpu 6.679 total
実行結果を見る限り内部で叩かれているコマンドは同じそうなので、やっぱりほとんど変化ないですね。
サーバーサイドレンダリング速度
これは負荷ツールを利用して計測します。
負荷ツールはJMeterとか色々あるんですが、個人的に最近良さげと思っているVegetaで検証します。
余談ですが、Githubの公式のド●ゴンボールの●ジータのイラストが堂々と貼ってあります。
そのうち鳥山先生に怒られろ。
GitHub - tsenart/vegeta: HTTP load testing tool and library. It's over 9000!
Vegeta Version 情報
vegeta -version Version: 12.8.4
npm vue-cli-service serve
$ echo 'GET http://localhost:1024' | vegeta attack -duration=5s | vegeta report Requests [total, rate, throughput] 250, 50.20, 50.17 Duration [total, attack, wait] 4.983s, 4.98s, 2.567ms Latencies [min, mean, 50, 90, 95, 99, max] 840.133µs, 1.785ms, 1.602ms, 2.267ms, 2.832ms, 6.55ms, 18.412ms Bytes In [total, mean] 234500, 938.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
Bun.sh vue-cli-service serve
$ echo 'GET http://localhost:3000' | vegeta attack -duration=5s | vegeta report Requests [total, rate, throughput] 250, 50.19, 50.17 Duration [total, attack, wait] 4.983s, 4.981s, 1.674ms Latencies [min, mean, 50, 90, 95, 99, max] 651.314µs, 1.798ms, 1.76ms, 2.015ms, 2.291ms, 2.556ms, 13.7ms Bytes In [total, mean] 180500, 722.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
結果だけ見るとほとんど変わらない結果になりました。 実行結果を見る限り内部で叩かれているコマンドは同じそうなので(vue-cli-service serve)、 Github内の下記の記述どおり、おそらくbunプロセスでは動いてない可能性があります。
bun run ${script-name} runs the equivalent of npm run script-name. For example, bun run dev runs the dev script in package.json, which may sometimes spin up non-bun processes.
そのため、それぞれサーバーを起動し確認してみます。
Node + express
$ echo 'GET http://localhost:1024' | vegeta attack -output=./node_report.bin -duration=5s | vegeta report ./node_report.bin Requests [total, rate, throughput] 250, 50.19, 50.17 Duration [total, attack, wait] 4.983s, 4.981s, 2.429ms Latencies [min, mean, 50, 90, 95, 99, max] 732.927µs, 2.92ms, 2.468ms, 3.575ms, 5.363ms, 26.182ms, 39.539ms Bytes In [total, mean] 234500, 938.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
Bun.serve
Bun プロセスで実行させるため、Bunに内臓されているhttpサーバー(Bun.server)で代用します。 expressで使えるserve-staticみたいなのがないか探していたところ良さげなものを見つけたのでこちらを利用しようと思います。
GitHub - jakobbouchard/serve-static-bun: Serve static files using Bun.serve or Bao.js
ちなみに、上記のexpressのコードを実行するとserve-staticがBun.shでは動きませんでした(おそらくバグ?)。 Bun.serve用のサーバーコード
import serveStatic from "serve-static-bun";
Bun.serve({ fetch: serveStatic("public"), port: 3000 });
実行コマンド
bun server_bun.js
計測結果
$ echo 'GET http://localhost:3000' | vegeta attack -output=./bun_report.bin -duration=5s | vegeta report ./bun_report.bin Requests [total, rate, throughput] 250, 50.21, 50.20 Duration [total, attack, wait] 4.98s, 4.979s, 1.594ms Latencies [min, mean, 50, 90, 95, 99, max] 577.129µs, 1.439ms, 1.402ms, 1.735ms, 1.921ms, 2.592ms, 3.517ms Bytes In [total, mean] 234500, 938.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
テキストで表示されてもちょっと見づらいのでプロットにします。
vegeta plot ./node_report.bin > node_prot.html vegeta plot ./bun_report.bin > bun_prot.html
左右に並べて見ると、平時は同じくらいですが、負荷が高くなるとnodeの方がlatencyが高く、Bunの方がはやいことがわかります。 左側がBun.serve、右側がnode + express
まとめ
- バグなのかnpm互換性が足りなく動かないことがある
- moduleのインストールは爆速になる
- サーバーサイドレンダリング&ページ初期表示は少し早くなる
- bun runで実行したコマンドはあまり速さは変わらない
moduleのインストール速度は爆速なので、新規参入タイミングでのコストは下がりそうですね。
また、大規模なクライアント開発なら速度改善の恩恵が受けられそう。
まだ出たてで、バグで動かないモジュールなども結構あるので、成熟するまで今後の動向を暖かく見守っていきたいですね。