Bun vs Node.js 效能對決

#bun
Bun vs Node.js 效能對決
五倍技術部
技術文章
Bun vs Node.js 效能對決

圖片來源

Bun

在當今的技術世界中,JavaScript 已經成為了一門不可或缺的程式語言。它不僅僅是網頁開發的基礎,也在 Server 端、Mobile App、甚至是 Desktop App,也都能看到 JavaScript 發揮著重要的作用。然而,隨著 JavaScript 生態系統的不斷發展和擴大,開發者面臨著越來越多的挑戰,例如程式碼的編譯、打包、測試和部署等,這些都需要不同的工具和環境來完成,使得開發流程變得複雜且難以管理。在這種背景下,一個名為 Bun 的新 JavaScript Runtime 橫空出世,而它的目的就是為現代的 JavaScript 生態系統提供一個更高效、整合的解決方案。

接下來,我們將深入探討 Bun,了解它如何解決開發者的實際問題,並通過一些例子來展示 Bun 的應用並且測試其效能。

Bun 解決了什麼?

Bun 的開發者強調,這個工具可以簡化 JavaScript 的開發過程,它使得許多 Node.js 工具,例如 node、npx、nodemon 和 dotenv 或 cross-env 等都變得不再必要。而且 Bun 可以運行多種檔案格式,例如常見的 .js.ts.jsx.tsx,這意味著它可以取代像 tsc 和 babel 這樣的轉譯器。在測試方面,Bun 可以跟 Jest 相容,支援 snapshot 測試、覆蓋率和 mocking。此外,Bun 還是一款性能優異的 JavaScript 打包工具,並提供與 esbuild 相容的 API。最後,它也是一款與 NPM 相容的套件管理器。

不僅僅是另一種 JavaScript Runtime

當提到 JavaScript 的 Runtime,實際上是在描述一個能夠執行 JavaScript 代碼的系統。大多數開發者多少都知道 Chrome 和 Node.js 背後的 V8 引擎,但 Bun 選擇了一條不同的路,它採用了 JavascriptCore 作為其動力。這是由 Apple 為 Safari 瀏覽器打造的, JavascriptCore 是一個極度注重性能的解決方案。

然而,僅憑 JavascriptCore 引擎是不夠的。為了創建一個完整的 JavaScript Runtime,Bun 在這方面選擇從零開始使用 Zig 實作 API。Zig 是一種低階程式語言,與 C 或 Rust 類似,專門為了建立高性能的應用程式而設計。

這種方式不僅提供了更好的性能和記憶體管理,而且在啟動和運作時都確保了驚人的速度。結合了這些特點,Bun 確實已經成為了 Node.js 的有力競爭者。

套件管理效能測試

Bun 主打的部分還有安裝速度遠超其他的 npm、yarn,還有目前最快的 pnpm,所以來實測看看是否真的這麼快。

Next.js

先用 pnpm 測試安裝 Next.js,所有選項都用預設值:

最後 pnpm 的安裝時間一共是 4.83 秒。

Next.js 安裝時間

Bun

接著用 Bun 來測試安裝 Next.js,選項也一樣用預設值:

最後 Bun 的安裝時間一共是 0.23 秒。

Bun 安裝時間

Yarn

順便補上 yarn 的安裝時間,一共花了 30.91 秒。

yarn 安裝時間

透過實際的測試,Bun 在安裝速度上的優勢非常明顯,毫無疑問地展示了極高的效能。

Server 效能測試

這部分將展示 Bun 和 Node.js Server 的性能測試。我們會為 Bun 和 Node.js 分別建立一個簡單的 Server,並使用 wrk 工具進行壓力測試,以評估它們在不同情況下的性能表現。

Bun

import { serve } from 'bun'
import qrcode from 'qrcode'

// QR code
async function generateQRCode(req) {
  try {
    const body = await req.json() // 使用 req.json() 方法來解析請求的 JSON 資料
    const text = body.text
    const qrCodeUrl = await qrcode.toDataURL(text)
    return new Response(JSON.stringify({ qrCodeUrl }), {
      headers: { 'Content-Type': 'application/json' }
    })
  } catch (error) {
    return new Response(error.message, { status: 500 })
  }
}

// 回應 Hello World
function respondHelloWorld() {
  return new Response('Hello World', {
    headers: { 'Content-Type': 'text/plain' },
    status: 200
  })
}

// 處理 HTTP 請求
async function fetch(req) {
  const url = new URL(req.url)
  if (req.method === 'POST' && url.pathname === '/bun-generate-qrcode') {
    return await generateQRCode(req)
  }
  if (req.method === 'GET' && url.pathname === '/bun-hello') {
    return respondHelloWorld()
  }
  return new Response('Not Found', { status: 404 })
}

// 啟動服務器
serve({
  fetch,
  port: 3000
})

console.log('Server is running on port 3000')

Node.js

const http = require('http')
const QRCode = require('qrcode')
const { parse } = require('querystring')

// QR code
async function generateQRCode(req, res) {
  let body = ''
  req.on('data', (chunk) => {
    body += chunk.toString()
  })
  req.on('end', async () => {
    const text = JSON.parse(body).text
    try {
      const qrCodeUrl = await QRCode.toDataURL(text)
      res.writeHead(200, { 'Content-Type': 'application/json' })
      res.end(JSON.stringify({ qrCodeUrl }))
    } catch (error) {
      res.writeHead(500, { 'Content-Type': 'text/plain' })
      res.end(error.message)
    }
  })
}

// 回應 Hello World
function respondHelloWorld(req, res) {
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  res.end('Hello World')
}

// 建立 HTTP Server
const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/node-generate-qrcode') {
    generateQRCode(req, res)
  } else if (req.method === 'GET' && req.url === '/node-hello') {
    respondHelloWorld(req, res)
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' })
    res.end('Not Found')
  }
})

server.listen(3000, () => {
  console.log('Server is running on port 3000')
})

在這兩個服務器實現中,我們分別提供了兩個路由,/hello/generate-qrcode。其中,/hello 路由將回傳 “Hello World” 文字,而 /generate-qrcode 則是會產生一個 QR Code 圖片。

為了更方便區分兩個 Server 的回應,我們將 Bun Server 的 /hello 路由改為 /bun-hello,將 Node.js Server 的 /hello 路由改為 /node-hello,以此類推,也將 /generate-qrcode 的部分做相同的修改。

接下來,我們使用 wrk 工具來進行壓力測試。

首先測試 GET:

指標 Node.js Bun
請求每秒 (RPS) 56462.43 71974.89
平均延遲 4.37 毫秒 3.28 毫秒
最大延遲 150.14 毫秒 36.42 毫秒
吞吐量 9.58 MB/秒 7.76 MB/秒
  • Bun 在每秒請求量(RPS)和平均延遲上優於 Node.js。
  • Node.js 的吞吐量稍高,可能是由於 Node.js 有較好的資料傳輸效率。
  • Node.js 的最大延遲遠高於 Bun,這可能是由於 Node.js 在處理極端情況時的表現不如 Bun。

接著測試 POST:

指標 Node.js Bun
請求每秒 (RPS) 1051.85 810.91
平均延遲 225.04 毫秒 291.27 毫秒
最大延遲 364.67 毫秒 644.04 毫秒
吞吐量 1.20 MB/秒 0.88 MB/秒
  • 在每秒請求量(RPS)上,Node.js 的表現優於 Bun。
  • Bun 的平均延遲較高,這可能是因為 Bun 在處理 POST 請求時的效率較低。
  • Node.js 的吞吐量也略高,這可能是因為 Node.js 在處理資料傳輸時的效率較高。
  • Bun 的最大延遲較高,這可能是 Bun 在處理極端情況時的效率較低。

綜合分析 GET 和 POST 的測試結果,我們可以得出以下結論:

Bun 在 GET 請求的處理上展現出很強的性能優勢,尤其是在每秒請求量和延遲方面。這可能使得 Bun 更適合於讀取密集型的應用或服務。

反之,Node.js 在處理 POST 請求時表現較好,特別是在每秒請求量和吞吐量方面。這可能意味著 Node.js 在寫入密集型或資料傳輸密集型的應用中可能會有較好的表現。

結論

綜上所述,Bun 作為一個新興的 JavaScript Runtime,其快速的套件安裝速度、良好的性能和簡化的開發流程無疑為 JavaScript 生態系統帶來了新的可能。特別是在套件管理效能測試和服務器效能測試中,Bun 顯示出了相當的優勢。然而,每個工具都有其特定的適用場景和局限性,開發者在選擇使用 Bun 或 Node.js 時,應根據自身的項目需求和團隊經驗來做出判斷。隨著 Bun 的不斷發展和完善,我們有理由相信它會成為 JavaScript 生態系統中的重要一員。