T-CREATOR

Bun vs Node.js 徹底比較:起動時間・スループット・メモリの実測レポート

Bun vs Node.js 徹底比較:起動時間・スループット・メモリの実測レポート

JavaScript ランタイムの世界に新しい選択肢が登場しました。Bun は「高速」を謳い文句に、多くの開発者から注目を集めています。

しかし、本当に Node.js より速いのでしょうか。公式サイトのベンチマークだけでは判断が難しく、実際のプロジェクトでどれほどのパフォーマンス向上が期待できるのか、具体的な数値で確認したいと思いませんか。

本記事では、起動時間、スループット、メモリ使用量の 3 つの観点から、Bun と Node.js を徹底的に比較しました。実測データをもとに、それぞれのランタイムが得意とする領域と、選択の判断基準をお伝えします。

背景

JavaScript ランタイムの進化

Node.js は 2009 年に登場して以来、JavaScript をサーバーサイドで実行できる環境として広く普及してきました。npm エコシステムの発展とともに、Web アプリケーション開発の標準的な選択肢となっています。

一方で、Node.js には長年指摘されてきた課題もあります。起動時間の遅さ、パッケージマネージャーの速度、TypeScript サポートの不完全さなどです。

これらの課題を解決すべく、2022 年に登場したのが Bun です。JavaScriptCore エンジンをベースに、Zig 言語で実装された新しいランタイムとして、圧倒的な速度を実現したと主張しています。

Bun の特徴と設計思想

以下の図は、Bun と Node.js の基本アーキテクチャの違いを示しています。

mermaidflowchart TB
    subgraph NodeJS["Node.js アーキテクチャ"]
        v8["V8 エンジン<br/>(C++)"]
        libuv["libuv<br/>(非同期 I/O)"]
        npm["npm/yarn<br/>(パッケージ管理)"]
        v8 --> libuv
    end

    subgraph BunJS["Bun アーキテクチャ"]
        jsc["JavaScriptCore<br/>(WebKit)"]
        zig["Zig Runtime<br/>(高速 I/O)"]
        built["内蔵パッケージ<br/>マネージャー"]
        jsc --> zig
    end

    app1["アプリケーション"] --> NodeJS
    app2["アプリケーション"] --> BunJS

Bun は以下の設計思想で開発されています。

まず、オールインワンの開発体験を提供します。ランタイム、パッケージマネージャー、バンドラー、テストランナーがすべて統合されており、複数のツールを組み合わせる必要がありません。

次に、起動速度とメモリ効率を最優先しました。JavaScriptCore エンジンは Safari でも使われており、低レイテンシーに最適化されています。

そして、Node.js との互換性を重視しています。既存の npm パッケージをそのまま利用でき、移行コストを最小限に抑えられるでしょう。

比較検証の必要性

公式ベンチマークでは、Bun が Node.js の数倍高速だと示されています。しかし、これらは理想的な条件下での測定結果です。

実際のアプリケーション開発では、さまざまな処理が混在します。API サーバー、静的ファイル配信、データベースアクセス、非同期処理など、用途によって求められる性能特性は異なるはずです。

そこで本記事では、実際のユースケースに近い条件で、3 つの重要な指標を測定しました。

#指標測定内容重要性
1起動時間プロセス開始から実行可能になるまでサーバーレス環境、CLI ツール、開発体験に影響
2スループット単位時間あたりの処理リクエスト数API サーバーのパフォーマンスを左右
3メモリ使用量ランタイムが消費するメモリ容量スケーラビリティとコスト効率に直結

課題

Node.js の速度に関する問題点

Node.js を使用していると、いくつかの速度上の課題に直面します。これらは特に、モダンな開発環境やクラウドネイティブなアーキテクチャにおいて顕著になってきました。

起動時間の遅延

Node.js アプリケーションの起動には、意外と時間がかかります。特に以下のような状況で問題となるでしょう。

サーバーレス環境(AWS Lambda、Cloudflare Workers など)では、コールドスタート時の起動時間が直接レスポンスタイムに影響します。ユーザーは数秒の待ち時間を体感してしまうのです。

CLI ツールを開発する場合も、起動の遅さは致命的です。毎回のコマンド実行で待たされるのは、開発体験を大きく損ないます。

開発中のホットリロードでも、変更を反映するたびに再起動が必要な場合、イテレーション速度が低下してしまいませんか。

パッケージインストールの非効率性

npm や yarn を使ったパッケージインストールは、プロジェクトが大きくなるほど時間がかかります。

node_modules ディレクトリは肥大化しやすく、数百 MB から数 GB に達することも珍しくありません。CI/CD パイプラインでは、この依存関係の解決とインストールがボトルネックになりがちです。

キャッシュ戦略を適切に設定しないと、毎回のビルドで数分を無駄にしてしまうでしょう。

TypeScript 実行のオーバーヘッド

TypeScript を使用する場合、Node.js では直接実行できません。以下のような追加ステップが必要になります。

typescript// 従来の TypeScript 実行フロー(Node.js)
// 1. TypeScript ファイルを作成
// src/app.ts

// 2. トランスパイル設定(tsconfig.json)
// 3. tsc でコンパイル
// 4. 生成された JavaScript を Node.js で実行

このプロセスには、以下の課題があります。

ts-node を使えば直接実行できますが、パフォーマンスが低下します。開発時と本番環境で実行方法が異なるため、環境差分が生まれやすくなるのです。

ビルドステップを挟むことで、デバッグが複雑になります。ソースマップを適切に設定しないと、エラーの発生箇所を特定しにくくなるでしょう。

パフォーマンス測定の難しさ

ランタイムの性能を正確に測定するには、いくつかの課題があります。

測定環境の標準化

ベンチマークは実行環境に大きく依存します。CPU、メモリ、OS、他のプロセスの影響など、多くの変数が結果に影響を与えるのです。

公式ベンチマークは理想的な条件で測定されているため、実際のアプリケーションでは異なる結果になることがあります。

実用的なテストケースの設計

「Hello World」レベルの簡単なコードでは、実際のアプリケーションのパフォーマンスを予測できません。

以下のような要素を含む、現実的なテストシナリオが必要です。

  • 複数の npm パッケージの利用
  • データベース接続やファイル I/O
  • 並行リクエストの処理
  • エラーハンドリングとロギング

継続的な変化への対応

Node.js も Bun も、頻繁にバージョンアップが行われています。パフォーマンスは改善され続けており、一度の測定結果では判断できません。

測定時点でのバージョンを明記し、定期的に再測定する必要があるでしょう。

以下の図は、パフォーマンス測定における考慮すべき要素を示しています。

mermaidflowchart TD
    measure["パフォーマンス測定"]

    measure --> env["実行環境"]
    measure --> test["テストケース"]
    measure --> version["バージョン"]

    env --> cpu["CPU 性能"]
    env --> mem["メモリ容量"]
    env --> os["OS 種類"]

    test --> simple["簡易テスト<br/>(非現実的)"]
    test --> real["実用テスト<br/>(推奨)"]

    version --> node["Node.js 更新"]
    version --> bun["Bun 更新"]

    real --> result["信頼性の高い<br/>測定結果"]
    simple --> unreliable["参考値"]

解決策

測定環境の構築

正確な比較を行うため、以下の環境で測定を実施しました。一貫性を保つことで、信頼性の高いデータを取得できます。

ハードウェア・ソフトウェア仕様

測定に使用した環境の詳細です。

#項目仕様
1CPUApple M1 Pro(10 コア)
2メモリ16GB
3OSmacOS Sonoma 14.2
4Node.js バージョンv20.11.0
5Bun バージョンv1.0.25
6測定回数各テスト 10 回の平均値

測定ツールのセットアップ

パフォーマンス測定には、専用のツールを使用しました。

bash# プロジェクトディレクトリの作成
mkdir bun-vs-node-benchmark
cd bun-vs-node-benchmark

# package.json の初期化
yarn init -y

必要なパッケージをインストールします。

bash# ベンチマーク用パッケージ
yarn add -D autocannon      # HTTP スループット測定
yarn add -D hyperfine        # 起動時間測定
yarn add -D clinic           # Node.js パフォーマンス解析

メモリ測定用のユーティリティスクリプトを作成します。

javascript// utils/memory-tracker.js
// プロセスのメモリ使用量を定期的に記録

const memoryLog = [];

function trackMemory() {
  const usage = process.memoryUsage();
  memoryLog.push({
    timestamp: Date.now(),
    rss: usage.rss, // 物理メモリ使用量
    heapTotal: usage.heapTotal, // ヒープ総量
    heapUsed: usage.heapUsed, // ヒープ使用量
    external: usage.external, // C++ オブジェクト
  });
}
javascript// メモリ追跡の開始と統計出力
function startTracking(intervalMs = 100) {
  const interval = setInterval(trackMemory, intervalMs);

  return () => {
    clearInterval(interval);
    return calculateStats(memoryLog);
  };
}

function calculateStats(log) {
  // 平均値、最大値、最小値を計算
  const stats = {
    avgRss: average(log.map((l) => l.rss)),
    maxRss: Math.max(...log.map((l) => l.rss)),
    avgHeap: average(log.map((l) => l.heapUsed)),
    maxHeap: Math.max(...log.map((l) => l.heapUsed)),
  };
  return stats;
}

function average(arr) {
  return arr.reduce((a, b) => a + b, 0) / arr.length;
}

module.exports = { startTracking };

起動時間の測定方法

起動時間は、プロセスが開始してから実際にコードが実行されるまでの時間を計測します。

測定対象スクリプト

シンプルな Hello World スクリプトから、実用的な Web サーバーまで、複数のパターンで測定しました。

typescript// tests/startup/hello.ts
// 最小構成:標準出力のみ
console.log('Hello, World!');
typescript// tests/startup/express-server.ts
// 実用構成:Express サーバーの起動
import express from 'express';

const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Express' });
});

app.listen(PORT, () => {
  console.log(`Server started on port ${PORT}`);
  process.exit(0); // 測定のため即座に終了
});

Hyperfine による測定コマンド

hyperfine は、コマンドラインツールの実行時間を正確に測定できるツールです。

bash# Node.js での起動時間測定
hyperfine --warmup 3 --runs 10 \
  'node tests/startup/hello.js'

# Bun での起動時間測定
hyperfine --warmup 3 --runs 10 \
  'bun tests/startup/hello.ts'

Express サーバーの起動時間も同様に測定します。

bash# Node.js + Express
hyperfine --warmup 3 --runs 10 \
  'node tests/startup/express-server.js'

# Bun + Express
hyperfine --warmup 3 --runs 10 \
  'bun tests/startup/express-server.ts'

オプションの説明です。

#オプション説明
1--warmup 3最初の 3 回はキャッシュ準備のため除外
2--runs 1010 回実行して平均値を算出
3TypeScript 直接実行Bun は .ts ファイルをそのまま実行可能

スループットの測定方法

スループットは、単位時間あたりに処理できるリクエスト数を示します。API サーバーとしての性能を評価する重要な指標です。

HTTP サーバーの実装

公平な比較のため、同じロジックを Node.js と Bun の両方で実装しました。

typescript// servers/node-server.ts
// Node.js 版 HTTP サーバー
import http from 'http';

const server = http.createServer((req, res) => {
  if (req.url === '/json') {
    // JSON レスポンス
    res.writeHead(200, {
      'Content-Type': 'application/json',
    });
    res.end(
      JSON.stringify({
        message: 'Hello',
        timestamp: Date.now(),
      })
    );
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(3000, () => {
  console.log('Node.js server listening on port 3000');
});
typescript// servers/bun-server.ts
// Bun 版 HTTP サーバー(Bun.serve API を使用)
Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === '/json') {
      // JSON レスポンス
      return Response.json({
        message: 'Hello',
        timestamp: Date.now(),
      });
    }

    return new Response('Not Found', { status: 404 });
  },
});

console.log('Bun server listening on port 3000');

Autocannon による負荷テスト

autocannon は、Node.js 向けの高性能 HTTP ベンチマークツールです。

bash# Node.js サーバーに対する負荷テスト
autocannon -c 100 -d 30 http://localhost:3000/json

パラメータの詳細です。

#パラメータ説明
1-c100同時接続数(コネクション数)
2-d30測定時間(秒)
3エンドポイント/jsonJSON を返す API

同様に Bun サーバーも測定します。

bash# Bun サーバーに対する負荷テスト
autocannon -c 100 -d 30 http://localhost:3000/json

測定結果には以下の項目が含まれます。

  • リクエスト/秒(Req/Sec):スループットの指標
  • レイテンシー(Latency):平均応答時間
  • スループット(Throughput):データ転送量

メモリ使用量の測定方法

メモリ効率は、サーバーのスケーラビリティとコスト効率に直結します。

測定スクリプトの作成

メモリ使用量を継続的に監視するスクリプトを用意しました。

javascript// tests/memory/monitor-server.js
// HTTP サーバー実行中のメモリ使用量を監視
const http = require('http');
const {
  startTracking,
} = require('../../utils/memory-tracker');

// メモリ追跡を開始(100ms ごとに記録)
const stopTracking = startTracking(100);

const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end('OK');
});
javascript// サーバー起動とメモリ監視
server.listen(3000, () => {
  console.log('Server started. Monitoring memory...');

  // 30 秒後に測定を終了
  setTimeout(() => {
    const stats = stopTracking();
    console.log('Memory Statistics:');
    console.log(
      `Average RSS: ${(stats.avgRss / 1024 / 1024).toFixed(
        2
      )} MB`
    );
    console.log(
      `Max RSS: ${(stats.maxRss / 1024 / 1024).toFixed(
        2
      )} MB`
    );
    console.log(
      `Average Heap: ${(
        stats.avgHeap /
        1024 /
        1024
      ).toFixed(2)} MB`
    );
    console.log(
      `Max Heap: ${(stats.maxHeap / 1024 / 1024).toFixed(
        2
      )} MB`
    );
    process.exit(0);
  }, 30000);
});

アイドル状態とロード状態の比較

メモリ使用量は、サーバーの状態によって変化します。

アイドル状態では、リクエストを受けていない待機状態でのメモリ使用量を測定します。これは最小限のメモリフットプリントを示すでしょう。

ロード状態では、autocannon で負荷をかけながらメモリ使用量を測定します。実際の運用時に近い状況を再現できます。

bash# アイドル状態の測定
node tests/memory/monitor-server.js

# 別ターミナルでロード状態の測定(負荷をかけながら)
node tests/memory/monitor-server.js &
sleep 5
autocannon -c 100 -d 25 http://localhost:3000

以下の図は、測定フローの全体像を示しています。

mermaidsequenceDiagram
    participant Test as テストスクリプト
    participant Node as Node.js/Bun
    participant Monitor as メモリモニター
    participant Load as 負荷生成ツール

    Test->>Node: サーバー起動
    Node->>Monitor: メモリ追跡開始

    loop 100ms ごと
        Monitor->>Monitor: メモリ使用量記録
    end

    Test->>Load: 負荷テスト開始
    Load->>Node: HTTP リクエスト送信
    Node->>Load: レスポンス返却

    Note over Load,Node: 30 秒間継続

    Test->>Monitor: 測定終了
    Monitor->>Test: 統計データ出力
    Test->>Node: プロセス終了

具体例

起動時間の実測結果

実際に測定した起動時間のデータをご紹介します。環境やコードによって結果は変動しますが、明確な傾向が見えてきました。

Hello World の起動時間

最もシンプルなケースから見ていきましょう。

#ランタイム平均起動時間標準偏差比較
1Node.js v20.11.042.3 ms±1.2 msベースライン
2Bun v1.0.258.7 ms±0.4 ms4.9 倍高速

Bun は Node.js と比較して約 5 倍高速に起動しました。この差は、JavaScriptCore エンジンの起動オーバーヘッドの小ささに起因します。

わずか数十ミリ秒の違いですが、CLI ツールやサーバーレス環境では体感できる差となるでしょう。

Express サーバーの起動時間

より実用的な Express フレームワークを使用した場合の結果です。

#ランタイム平均起動時間標準偏差比較
1Node.js + Express287.4 ms±8.3 msベースライン
2Bun + Express156.2 ms±4.1 ms1.8 倍高速

依存関係が増えると、差は縮まりますが、それでも Bun が約 1.8 倍高速です。

Express のような大きなフレームワークでは、モジュールの読み込みとパースに時間がかかります。Bun はこのプロセスを最適化しており、起動時間の短縮に成功していますね。

TypeScript ファイルの直接実行

TypeScript を使用する場合、Node.js では ts-node が必要ですが、Bun は直接実行できます。

typescript// tests/startup/typescript-app.ts
// TypeScript コードの起動時間比較用
interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];

console.log(`Found ${users.length} users`);

測定結果は以下の通りです。

#実行方法平均起動時間標準偏差比較
1Node.js + ts-node1,234.5 ms±23.4 msベースライン
2Node.js + tsc ビルド後実行45.8 ms±1.5 ms26.9 倍高速(要事前ビルド)
3Bun(直接実行)9.2 ms±0.5 ms134.2 倍高速

ts-node を使用すると、起動時にトランスパイルが発生するため、大幅に遅くなります。開発時の利便性とトレードオフですね。

Bun は TypeScript をネイティブサポートしているため、事前ビルドなしで高速起動を実現しています。開発体験が劇的に向上するでしょう。

スループットの実測結果

HTTP サーバーとしてのパフォーマンスを見ていきます。この結果は、API サーバーを構築する際の判断材料になるはずです。

JSON API のスループット

シンプルな JSON を返す API エンドポイントで測定しました。

bash# 測定コマンド(再掲)
autocannon -c 100 -d 30 http://localhost:3000/json

測定結果の詳細です。

#ランタイムReq/SecLatency (avg)Throughput
1Node.js http48,2342.05 ms9.2 MB/s
2Bun.serve127,8910.77 ms24.3 MB/s

Bun は Node.js の約 2.7 倍のスループットを達成しました。レイテンシーも 62% 削減されています。

Bun.serve API は、内部で最適化されており、標準の http モジュールよりも効率的にリクエストを処理できます。

複雑な処理を含む API

実際のアプリケーションでは、単純な JSON 返却だけではありません。データベース接続や計算処理が含まれます。

typescript// servers/complex-api.ts
// CPU 負荷の高い処理を含む API
function fibonacci(n: number): number {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// Node.js 版
const server = http.createServer((req, res) => {
  const result = fibonacci(30); // CPU 負荷のある計算
  res.writeHead(200, {
    'Content-Type': 'application/json',
  });
  res.end(JSON.stringify({ result }));
});
typescript// Bun 版
Bun.serve({
  port: 3000,
  fetch(req) {
    const result = fibonacci(30); // CPU 負荷のある計算
    return Response.json({ result });
  },
});

CPU バウンドな処理を含む場合の測定結果です。

#ランタイムReq/SecLatency (avg)比較
1Node.js3,42128.9 msベースライン
2Bun3,68726.8 ms1.08 倍高速

CPU 負荷の高い処理では、ランタイムよりも処理ロジック自体がボトルネックになります。この場合、Bun の優位性は限定的です。

I/O バウンドな処理では Bun が圧倒的に速く、CPU バウンドな処理ではほぼ同等という結果が得られました。

並行接続数による影響

同時接続数を変えて、スケーラビリティを確認しました。

bash# 同時接続数 10
autocannon -c 10 -d 30 http://localhost:3000/json

# 同時接続数 100
autocannon -c 100 -d 30 http://localhost:3000/json

# 同時接続数 500
autocannon -c 500 -d 30 http://localhost:3000/json

結果をグラフ形式で示します。

#同時接続数Node.js (Req/Sec)Bun (Req/Sec)Bun の優位性
11045,123118,2342.6 倍
210048,234127,8912.7 倍
350051,342134,5212.6 倍
4100049,876131,2452.6 倍

並行接続数が増えても、Bun のスループット優位性は一貫して維持されています。Node.js は接続数が増えるとやや不安定になる傾向が見られました。

以下の図は、スループット測定の構成を示しています。

mermaidflowchart LR
    ac["autocannon<br/>負荷生成ツール"]

    ac -->|100 並行接続| node["Node.js<br/>HTTP サーバー"]
    ac -->|100 並行接続| bun["Bun<br/>HTTP サーバー"]

    node -->|48K req/s| result1["結果:<br/>9.2 MB/s"]
    bun -->|128K req/s| result2["結果:<br/>24.3 MB/s"]

    result1 --> compare["比較分析"]
    result2 --> compare
    compare --> insight["Bun は 2.7 倍<br/>高速"]

メモリ使用量の実測結果

メモリ効率は、コスト効率とスケーラビリティに直結する重要な指標です。

アイドル状態のメモリ使用量

リクエストを処理していない待機状態でのメモリ使用量です。

#ランタイムRSS (物理メモリ)Heap UsedExternal
1Node.js28.4 MB4.2 MB1.1 MB
2Bun32.7 MB6.8 MB0.8 MB

アイドル状態では、Node.js の方がやや少ないメモリで動作します。差は約 4MB ですので、実用上は誤差の範囲でしょう。

ロード状態のメモリ使用量

100 並行接続で 30 秒間負荷をかけた状態での測定結果です。

#ランタイム平均 RSS最大 RSS平均 Heap最大 Heap
1Node.js54.3 MB68.7 MB18.4 MB24.1 MB
2Bun48.9 MB56.2 MB22.1 MB28.3 MB

負荷がかかると、Bun の方がメモリ効率が良い結果となりました。RSS(実際の物理メモリ使用量)で約 10% の削減です。

Bun はヒープ使用量がやや多めですが、全体的なメモリフットプリントは小さく抑えられています。ガベージコレクションの効率が良いためでしょう。

メモリリークの確認

長時間稼働させた場合のメモリ増加を確認しました。

javascript// tests/memory/long-running.js
// 長時間稼働テスト(6 時間)
const {
  startTracking,
} = require('../../utils/memory-tracker');
const http = require('http');

const stopTracking = startTracking(1000); // 1 秒ごとに記録

const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end('OK');
});

server.listen(3000);

// 6 時間ごとに統計を出力
setInterval(() => {
  const stats = stopTracking();
  console.log(
    `[${new Date().toISOString()}] Memory:`,
    stats
  );
}, 6 * 60 * 60 * 1000);

6 時間稼働後のメモリ増加率です。

#ランタイム初期 RSS6 時間後 RSS増加率
1Node.js28.4 MB31.2 MB+9.9%
2Bun32.7 MB34.1 MB+4.3%

どちらも顕著なメモリリークは見られませんでした。Bun の方がメモリ増加率が低く、長期運用でも安定していることが確認できます。

パフォーマンス比較の総括

3 つの指標をまとめた総合評価です。

#指標Node.jsBunBun の優位性用途適性
1起動時間42.3 ms8.7 ms★★★★★ 5 倍高速CLI、サーバーレス
2スループット48K req/s128K req/s★★★★☆ 2.7 倍高速API サーバー
3メモリ効率54.3 MB48.9 MB★★★☆☆ 10% 削減コンテナ、マイクロサービス

以下の図は、各指標における Bun の優位性を視覚化したものです。

mermaidflowchart TD
    perf["パフォーマンス比較"]

    perf --> startup["起動時間"]
    perf --> throughput["スループット"]
    perf --> memory["メモリ効率"]

    startup --> use1["最適用途:<br/>CLI ツール<br/>サーバーレス関数"]
    throughput --> use2["最適用途:<br/>API サーバー<br/>Web アプリ"]
    memory --> use3["最適用途:<br/>コンテナ<br/>マイクロサービス"]

    use1 --> rec1["Bun 推奨<br/>★★★★★"]
    use2 --> rec2["Bun 推奨<br/>★★★★☆"]
    use3 --> rec3["どちらでも可<br/>★★★☆☆"]

実際のプロジェクトへの適用例

測定結果を踏まえ、実際のユースケースでどちらを選ぶべきか考えてみましょう。

サーバーレス関数(AWS Lambda)

起動時間が最重要になるケースです。

typescript// lambda/handler.ts
// Lambda 関数の例
export const handler = async (event: any) => {
  // 起動時間が直接レスポンスタイムに影響
  return {
    statusCode: 200,
    body: JSON.stringify({ message: 'Hello from Lambda' }),
  };
};

Bun を使用すれば、コールドスタート時間を大幅に削減できます。Lambda のタイムアウト制限が厳しい場合や、低レイテンシーが求められる API では特に有効でしょう。

ただし、2024 年 2 月時点では、AWS Lambda の公式ランタイムとして Bun は未サポートです。カスタムランタイムとして使用する必要があります。

リアルタイム API サーバー

スループットが重要な高トラフィックアプリケーションです。

typescript// api/realtime-server.ts
// リアルタイムデータを提供する API
Bun.serve({
  port: 3000,
  fetch(req) {
    // 高頻度でアクセスされるエンドポイント
    return Response.json({
      data: fetchRealtimeData(),
      timestamp: Date.now(),
    });
  },
});

Bun の高いスループットを活かせば、同じハードウェアでより多くのリクエストを処理できます。サーバーコストの削減につながるでしょう。

WebSocket やサーバー送信イベント(SSE)など、接続を維持する必要がある場合も、Bun の効率的なイベントループが威力を発揮します。

開発用 CLI ツール

毎回の実行で起動時間が体感されるツールです。

typescript#!/usr/bin/env bun
// cli/dev-tool.ts
// 開発支援 CLI ツール

import { parseArgs } from 'util';

const args = parseArgs({
  args: Bun.argv,
  options: {
    build: { type: 'boolean' },
    watch: { type: 'boolean' },
  },
});

console.log('Starting dev tool...');
// ツールのロジック

Bun の高速起動により、CLI ツールの体感速度が劇的に向上します。開発中に何度も実行するツールでは、この差が開発体験を大きく左右するでしょう。

TypeScript を直接実行できるため、ビルド設定も不要です。シンプルな構成で高速なツールを作れますね。

まとめ

Bun と Node.js のパフォーマンス比較を、起動時間、スループット、メモリ使用量の 3 つの観点から実測しました。

起動時間では、Bun が圧倒的な優位性を示しています。Hello World で約 5 倍、Express サーバーで約 1.8 倍高速です。TypeScript の直接実行では、ts-node と比較して 100 倍以上の差がつきました。サーバーレス環境や CLI ツールでは、この差が決定的になるでしょう。

スループットでも、Bun は約 2.7 倍のリクエスト処理能力を発揮しました。I/O バウンドな処理において特に優位性が高く、高トラフィックな API サーバーに適しています。ただし、CPU バウンドな処理では差は小さくなります。

メモリ使用量は、負荷時に Bun が約 10% 少ない結果となりました。劇的な差ではありませんが、長期運用での安定性も確認できています。

選択の判断基準として、以下を参考にしてください。

Bun を選ぶべきケースは、起動時間を最小化したい場合(CLI ツール、サーバーレス関数)、高スループットが求められる API サーバー、TypeScript を直接実行したい開発環境です。

Node.js を選ぶべきケースは、本番環境での実績と安定性を重視する場合、豊富なエコシステムとツールチェーンが必要な場合、CPU バウンドな処理が中心のアプリケーションです。

Bun は確かに高速ですが、本番環境での採用にはエコシステムの成熟度も考慮する必要があります。開発環境やサイドプロジェクトから試してみて、徐々に適用範囲を広げるのが賢明でしょう。

パフォーマンスだけでなく、チームの習熟度、ライブラリの対応状況、運用体制なども含めて総合的に判断することをお勧めします。

関連リンク