Bun vs Node.js:深度对比与底层架构解析

1 阅读9分钟

Bun vs Node.js:深度对比与底层架构解析

前言

2025年,随着 Anthropic 收购 Bun 并将其打造为 AI Agent 的首选运行环境,JavaScript 运行时之战进入了全新阶段。本文基于一次深度的技术对话,系统性地拆解了 Bun 的技术本质、Node.js 的构成、以及 Linux 高性能 I/O 接口的演进历程。gemini.google.com/app/351f377…


一、Bun 是什么?为什么它值得关注?

Bun 是一个高性能、一体化的 JavaScript/TypeScript 运行时和工具包,由 Jarred Sumner 于 2021 年首次发布。它不仅仅替代 Node.js(作为运行时),还内置了:

  • 包管理器(替代 npm/yarn/pnpm)
  • 打包器(替代 Webpack/Vite/esbuild)
  • 测试运行器(替代 Jest/Vitest)
  • 内置 SQLitebun:sqlite

1.1 技术栈解析

维度Node.jsBun
底层引擎V8 (Google)JavaScriptCore (Apple)
核心语言C++Zig
启动速度较慢(V8 优化长期运行)极快(JSC 优化冷启动)
TS 支持需要外部转译(tsc/tsx/ts-node)原生支持(直接运行 .ts/.tsx)
模块系统区分 CJS/ESM 较麻烦无缝兼容(同一个文件可混用 import 和 require)
生态兼容性行业标准,100% 兼容约 95%+ 兼容,极少数底层 C++ Addon 不支持

1.2 为什么选择 JavaScriptCore 而不是 V8?

这个问题触及了高性能运行时的核心取舍:

  1. 启动速度:V8 设计初衷是为长期运行的 Chrome 浏览器优化,有复杂的 JIT 预热过程。JSC 设计初衷是为移动端 Safari 优化,启动极快,适合 Serverless/CLI 场景。

  2. 内存占用:JSC 在内存分配上比 V8 更加节俭,更少的内存意味着更低的容器化部署成本。

  3. 架构可塑性:JSC 的 C API 相对更干净,Bun 作者可以用 Zig 编写更高效的"桥接代码",让 JS 变量和 Zig 内存之间的交换几乎零开销。


二、Bun 如何实现 CJS/ESM 无缝混用?

这是 Bun 最令人惊叹的特性之一,也是最容易引发误解的地方。

2.1 核心原理:编译时"实时转译"

Bun 在执行代码之前,会通过其高度优化的 Zig 编写的转译器对源码进行扫描:

  1. 预转译 (Transpilation):将 ESM 模块实时转换为类似 CJS 的格式
  2. 统一为 Binding 模式:在 JS 引擎内部,所有模块(包括 CJS)都表现得像 ESM,建立引用关系而非值拷贝

2.2 "转译为 CJS"与"表现得像 ESM"矛盾吗?

不矛盾,这是"手段"与"目标"的关系

  • 手段:为了能让 require() 同步调用 ESM,Bun 必须在语法层面把 export default 变成类似 module.exports 的形式
  • 目标:在 JSC 引擎内部,Bun 的 require 返回的是一个实时代理(Live Binding),而非快照值

举例说明:假设模块 A 导出一个变量 count,内部有定时器在增加它:

  • Node.js (CJS):require('./A') 拿到的是那一刻的值,即使 A 内部加到了 100,你手里的变量还是 0
  • Bun:无论用 import 还是 require,拿到的都是指向 A 模块内存地址的引用,当 A 内部变到 100,你读取到的也是 100

2.3 Tree Shaking 如何处理?

由于 Bun 本身就是打包器,Tree Shaking 是在 Zig 编写的转译阶段同步完成的:

  • 静态分析:Zig 以极快的速度扫描整个依赖图
  • 死代码删除 (DCE):基于"类 ESM"的引用模式,能清晰追踪到哪些导出从未被引用
  • 对 CJS 的"暴力"优化:常量折叠 + 属性追踪,即使是被导出为大对象的 CJS 包(如 lodash),如果只访问了 _.cloneDeep,其他属性也会被剔除

三、Bun 的架构:为什么不需要 libuv?

3.1 事件循环的本质

所有异步 JS 运行时都必须有事件循环。Bun 的事件循环不是"没有",而是用 Zig 直接对接系统内核实现。

Node.js 的逻辑(套娃模式)

JS 代码 -> V8 引擎 -> Node C++ 绑定 -> libuv -> 系统内核调用(epoll/kqueue/IOCP)

Bun 的逻辑(直连模式)

JS 代码 -> JSC 引擎 -> Zig 编写的内核驱动 -> 系统内核调用

3.2 libuv 的必要性还在吗?

libuv 的存在主要是为了跨平台一致性。它为 Node.js 解决了:

  • 网络 I/O:Linux 用 epoll、macOS 用 kqueue、Windows 用 IOCP
  • 文件 I/O:通过线程池模拟异步(因为 Unix/Linux 长期以来缺乏完美的原生异步文件接口)

Bun 选择舍弃 libuv,意味着必须为 Windows (IOCP)、Linux (io_uring/epoll)、macOS (kqueue) 手动维护三套高度复杂的底层代码。这也是 Bun Windows 版本开发进度较慢的根本原因。

3.3 舍弃 libuv 的优势与代价

优势代价
消除"抽象税",路径更短、指令更少跨平台维护成本极高
零内存分配的异步部分 Node C++ Addons 可能不兼容
纵向集成优化(Transpiler + Runtime 都是 Zig)社区验证的广度不足

四、epoll vs io_uring:高性能 I/O 接口演进

4.1 epoll 是什么?

epoll 是 Linux 内核中一种可扩展的 I/O 事件通知机制,允许一个线程同时监控成千上万个文件描述符。Nginx 能够支撑百万并发的核心原因就是使用 epoll。

核心组件

  • 红黑树:高效增删改查 10 万级连接,查找速度 O(logn)
  • 双向链表(Ready List):内核硬件中断触发回调,将就绪的 FD 移入此链表
  • 回调机制:不是轮询,而是内核事件触发

4.2 io_uring 的革命性创新

io_uring 是 Linux 5.1+(2019年)引入的高性能异步 I/O 框架,引入了双环形缓冲区 (Dual Ring Buffers) 架构:

  • 提交队列 (SQ):程序往里写任务(读取文件、发送数据包)
  • 完成队列 (CQ):内核处理完任务后填入结果
  • 核心架构:通过 mmap 映射共享内存,双方看同一块物理地址

4.3 io_uring 的三种通知模式

模式交互方式适用场景
默认模式程序调用 io_uring_enter(),内核完成后唤醒通用场景
轮询模式 (IOPoll)程序不断查看 CQ 尾指针NVMe 等低延迟存储
SQPOLL 模式内核线程死循环盯 SQ,0 次系统调用极致性能场景

4.4 为什么 io_uring 比 epoll 快?

  • 上下文切换:epoll 每次问"谁好了"都要系统调用(用户态→内核态切换),io_uring SQPOLL 模式下可做到 0 次切换
  • 数据拷贝:传统 I/O 需要内核缓冲区和用户缓冲区之间多次拷贝,io_uring 通过 mmap 让双方看同一块内存
  • 吞吐能力:支持批量提交和批量完成,能压榨硬件极限

五、libuv 的架构解析

libuv 是用 C 语言编写的,核心任务有两个:磨平跨平台差异 和 管理异步事件循环。

5.1 核心构成

  • 句柄 (Handles):长期占用资源的引用(TCP Server、定时器、信号量)
  • 请求 (Requests):短期的特定操作(读取文件、发送数据包)
  • 线程池:默认 4 个线程,用于处理无法直接利用内核异步能力的任务(文件 I/O、DNS 查询)
  • 事件循环:协调所有 Handle 和 Request

5.2 事件循环的七阶段

更新时间 → 定时器阶段 → 待定回调 → 空转/准备 → 轮询阶段 → 复检阶段 → 关闭回调

5.3 libuv 的局限性

  • 抽象开销:为了兼容性,在内存分配和数据传递上有"抽象税"
  • 文件 I/O 瓶颈:依赖线程池处理文件操作,大量小文件读写时线程切换开销大
  • 架构陈旧:设计时 io_uring 还未诞生,无法利用最先进的零拷贝、零切换特性

六、io_uring 是未来吗?

6.1 当前采用状态

项目io_uring 采用状态
Nginx通过模块支持,但默认仍用 epoll,更倾向于用 io_uring 处理磁盘 I/O
libuvNode.js 20+ 在 Linux 上已用 io_uring 优化部分磁盘操作,网络仍倾向 epoll

6.2 三大阻碍因素

  1. 安全性问题:io_uring 早期版本被爆出多起 Linux 内核漏洞,很多公有云一度禁用了该系统调用
  2. 边际收益递减:对于普通 HTTP 请求,epoll 开销已经很小,在复杂业务逻辑面前不显著
  3. 内核版本滞后:io_uring 网络功能(如 multishot accept)直到 Linux 6.x 才真正成熟

6.3 未来判断

毫无疑问是未来。它的架构逻辑代表了操作系统的进化方向:

  • 统一化接口:终结"网络用 epoll,磁盘用 libaio,信号用 signalfd"的碎片化时代
  • AI 时代的加速器:大模型推理需要极致 I/O 和内存共享,io_uring 是唯一选择
  • 新一代软件的基石:Bun、PostgreSQL 17+、TigerBeetle 等从第一天就围绕 io_uring 构建

七、Anthropic 收购版图解析

2025年开始,Anthropic 为补齐 AI Agent 版图进行了精准收购:

收购对象时间补充能力
Bun2025年12月底层性能 — 让 AI 生成的代码能瞬间运行和测试
Vercept2026年2月环境交互 — 攻克 Computer Use,让 AI 能操作电脑
Humanloop2025年8月反馈链路 — 企业级评估与对齐

一句话总结:Anthropic 正在把自己从一个"模型厂商"变成一个**"具备执行能力的 AI 操作系统"**。


八、总结:如何选择?

8.1 前端领域:必试 Bun

  • bun install 比 npm 快 10-30 倍
  • 内置 TypeScript 原生支持,无需配置
  • CLI 工具和脚本编写体验极佳

8.2 服务端领域:视场景而定

场景推荐
内部工具/小工具Bun ⭐⭐⭐⭐⭐
Serverless/边缘计算Bun ⭐⭐⭐⭐⭐
高并发网关Bun ⭐⭐⭐⭐
金融级核心业务Node.js ⭐⭐⭐⭐⭐

8.3 本质总结

  • Node.js:社区驱动的组装机,灵活性高但有性能天花板
  • Bun:一体化锻造的利刃,用 Zig + JSC 组合拳打破性能极限
  • io_uring:Linux I/O 的未来,共享内存架构终结"上下文切换"噩梦
  • libuv:工业级稳定性的代表,正在缓慢但坚定地拥抱 io_uring

本文基于 2026 年 3 月的技术对话整理,部分信息可能随软件版本更新而变化。