Bun 的 runtime 是如何设计的?和 Node.js 的事件循环有何不同?
Bun 选用 JavaScriptCore 引擎 + Zig 语言实现运行时,采用"原生优先"架构绕过 libuv 抽象层,直接与操作系统 I/O 原语交互。Node.js 基于 V8 + libuv 的多阶段事件循环模型,两者在引擎选型、事件循环实现和性能表现上存在根本差异。
核心架构差异
Node.js 的执行链路:OS TCP → libuv 事件循环 → http_parser(C 绑定) → V8 JS 对象 → 处理函数
Bun 的执行链路:OS TCP → 原生 uWebSockets(C/Zig) → JavaScriptCore → 处理函数
Bun 少了一层 libuv 抽象,这是其性能优势的关键来源。具体差异:
| 维度 | Node.js | Bun |
|---|---|---|
| JS 引擎 | V8(Google) | JavaScriptCore(Apple) |
| 底层语言 | C/C++(libuv) | Zig(原生编译) |
| HTTP 实现 | libuv → http_parser → V8 | 原生 uWebSockets → JSC |
| 事件循环 | libuv 多阶段循环 | 原生优先,绕过 libuv 抽象 |
| 启动速度 | 基准 | 约 4 倍于 Node.js |
| 内存占用 | 较高 | 较低(JSC 内存效率更优) |
Node.js 事件循环的六个阶段
Node.js 基于 libuv 实现单线程事件循环,主要阶段依次为:
- Timers — 执行 setTimeout / setInterval 回调
- Pending callbacks — 执行上一轮延迟的 I/O 回调
- Idle, prepare — libuv 内部使用
- Poll — 检索新 I/O 事件,执行 I/O 相关回调
- Check — 执行 setImmediate 回调
- Close callbacks — 处理 socket.on("close", ...) 等关闭事件
每个阶段有独立的队列,当前阶段队列清空或达到最大回调数后进入下一阶段。这种设计成熟稳定,但 libuv 作为中间层引入了额外开销,且单线程模型下 CPU 密集型任务会阻塞整个循环。
Bun 的原生优先事件循环
Bun 不使用 libuv,而是用 Zig 原生实现事件循环核心逻辑:
- 原生 HTTP 服务器:基于 uWebSockets 的 C/Zig 实现,直接与 OS TCP socket 交互
- WebSocket 原生支持:内置服务器和客户端,无需第三方库
- 自动打包和转译:运行时内置打包器,TypeScript、JSX 开箱即用
代码示例:
javascriptBun.serve({ port: 3000, fetch(req) { return new Response("Hello from Bun!"); }, });
Bun.serve 不需要额外依赖,底层走原生实现路径,吞吐量约为 Node.js 的 3-4 倍。
性能对比的关键数据
- HTTP 吞吐量:Bun 约 52,000 req/s,Node.js 约 14,000 req/s,Bun.serve 比 Fastify 5 快约 2.8 倍
- 启动速度:Bun 进程启动约为 Node.js 的 4 倍,Serverless 和 CI 场景收益明显
- 包安装:Bun 内置包管理器利用硬链接和全局缓存,安装速度约为 npm 的 30 倍
兼容性与选型
Bun 对 Node.js API 兼容性已达 90% 以上,大部分 Express/Fastify 应用可直接运行,但需注意:
- 使用 N-API 的原生模块(如 node-pty)可能不兼容,因为它们深入 V8 内部或依赖 libuv
- 主线程都是单线程,CPU 密集型阻塞任务都会卡住事件循环,Bun 通过
Bun.spawn和 Worker 线程支持并行 - 生产案例仍在积累,Node.js 有十五年的稳定性验证
选型建议:新项目和 CI/开发环境优先考虑 Bun;深度依赖 N-API 或对稳定性要求极高的生产环境暂用 Node.js。
追问:Bun 的事件循环是完全绕过了 libuv 还是部分替代?
Bun 并非完全重写 libuv 的全部功能,而是对核心 I/O 路径做了原生替代。文件系统操作等非热路径仍使用类似 libuv 的异步封装。本质上 Bun 的策略是"热路径原生优化,冷路径兼容复用"——HTTP、WebSocket 等高频 I/O 走原生 C/Zig 实现,低频操作保持与 Node.js 行为一致。理解这一点就能解释为什么 Bun 既能在基准测试中大幅领先,又能保持高兼容性:它只在关键路径上做了优化,没有试图从零重建整个异步 I/O 体系。