Bun 多线程有多快?postMessage 传输字符串比 Node.js 快 400 倍!

Bun 多线程有多快?postMessage 传输字符串比 Node.js 快 400 倍!

Bun v1.2.21 为 postMessage 引入了字符串快速路径------传输 3MB 字符串仅需 593 纳秒,比 Node.js 快约 408 倍 ,内存占用减少 22 倍。本文带你全面了解 Bun Worker API 的使用方式与性能优化原理。


一、Worker 基础

Bun 实现了浏览器标准的 Web Workers API,让你在独立线程中运行 JavaScript,同时与主线程共享 I/O 资源。

与 Node.js 不同,Bun 的 Worker 开箱支持 TypeScript、JSX、ES Modules 和 CommonJS,无需任何构建工具。

创建 Worker

主线程:

ts 复制代码
const worker = new Worker("./worker.ts");
worker.postMessage("hello");

worker.onmessage = (event) => {
  console.log(event.data); // 'world'
};

Worker 线程(worker.ts):

ts 复制代码
declare var self: Worker;

self.onmessage = (event: MessageEvent) => {
  console.log(event.data); // 'hello'
  postMessage("world");
};

declare var self: Worker; 用于消除 TypeScript 类型报错。

预加载模块(preload

在 Worker 启动前加载监控、追踪等初始化代码:

ts 复制代码
const worker = new Worker("./worker.ts", {
  preload: ["./sentry.js", "./opentelemetry.js"],
});

blob: URL

从字符串或动态代码创建 Worker:

ts 复制代码
const blob = new Blob(
  [`self.onmessage = (e) => postMessage(e.data);`],
  { type: "application/typescript" }
);
const worker = new Worker(URL.createObjectURL(blob));

"open" 事件

Worker 就绪后触发(浏览器无此事件):

ts 复制代码
worker.addEventListener("open", () => {
  console.log("worker is ready");
});

消息会自动排队直到 Worker 就绪,无需等待 "open" 事件即可发送。


二、性能优化:为什么快 400 倍?

官方基准测试

Bun 官方在 GitHub 提供了测试数据:

字符串大小 Bun 1.2.21 Bun 1.2.20 Node.js 24.6.0
11 字符 543 ns 598 ns 806 ns
14 KB 460 ns 1,350 ns 1,220 ns
3 MB 593 ns 326,290 ns 242,110 ns

关键结论

  • 传输 3MB 字符串:Bun 仅需 593 ns ,Node.js 需 242,110 ns ------快约 408 倍
  • 字符串大小对 Bun 几乎无影响------11 字符和 3MB 耗时相近(543 ns vs 593 ns)。

技术原理

传统 postMessage 使用结构化克隆算法序列化数据------复制整个字符串到新缓冲区,再在另一端反序列化。

Bun 的核心洞察 :JavaScriptCore 引擎中的字符串是线程安全的引用计数对象:

cpp 复制代码
class StringImplShape {
    std::atomic m_refCount;  // 线程安全
    unsigned m_length;       // 不可变
    union {
        const LChar* m_data8;
        const char16_t* m_data16;
    };
    mutable unsigned m_hashAndFlags;
};

既然字符串本身线程安全,为何要复制?直接传递指针即可。

快速路径实现

cpp 复制代码
WTF::String toCrossThreadShareable(WTF::String& string) {
    auto* impl = string.impl();
    // 三类字符串必须序列化:Atom、Symbol、子字符串
    if (impl->isAtom() || impl->isSymbol() || 
        impl->bufferOwnership() == StringImpl::BufferSubstring)
        return string.isolatedCopy();
    impl->hash();
    impl->setNeverAtomicize();
    return string; // 零拷贝!直接共享指针
}

生效条件(全部满足时自动触发):

  • 使用 postMessagestructuredClone
  • 只发送字符串(不混发其他数据)
  • 字符串不是 Atom、Symbol 或子字符串
  • 发送到同一进程内的另一个线程
  • 字符串长度 ≥ 256 字符

简单对象快速路径

对于只包含基本类型(字符串、数字、布尔、null、undefined)的简单对象,Bun 同样有优化路径,性能提升 2-241 倍

Bun(优化后):

ts 复制代码
postMessage({ prop: "11 chars", ...9 more props }) - 648ns
postMessage({ prop: "14 KB string", ...9 more props }) - 719ns
postMessage({ prop: "3 MB string", ...9 more props })  - 1.26µs

Node.js v24.6.0(对比):

js 复制代码
postMessage({ prop: "11 chars", ...9 more props }) - 1.19µs
postMessage({ prop: "14 KB string", ...9 more props }) - 2.69µs
postMessage({ prop: "3 MB string", ...9 more props })  - 304µs

简单对象快速路径激活条件:

  • 纯对象(无原型链修改)
  • 仅包含可枚举、可配置的数据属性
  • 无非索引属性、getter/setter
  • 所有属性值都是基本类型或字符串

三、高级特性

生命周期管理(ref / unref

默认 Worker 会阻止主进程退出。unref() 可解耦生命周期:

ts 复制代码
const worker = new Worker("./worker.ts");
worker.unref(); // 主进程可独立退出
// 之后重新关联
worker.ref();

创建时也可设置:

ts 复制代码
new Worker("./worker.ts", { ref: false });

终止 Worker

自动终止:事件循环空闲时退出。强制终止:

ts 复制代码
worker.terminate();

Worker 内部可调用 process.exit(code) 自行退出,主线程收到 "close" 事件:

ts 复制代码
worker.addEventListener("close", (event) => {
  console.log("exit code:", event.code);
});

smol 模式(内存优化)

内存受限时启用,牺牲性能降低内存占用:

ts 复制代码
const worker = new Worker("./worker.ts", { smol: true });
// 将 JSC::HeapSize 从 Large 改为 Small

环境数据共享

ts 复制代码
// 主线程
import { setEnvironmentData } from "worker_threads";
setEnvironmentData("config", { apiUrl: "https://api.example.com" });

// Worker
import { getEnvironmentData } from "worker_threads";
const config = getEnvironmentData("config");

监听 Worker 创建

ts 复制代码
process.on("worker", (worker) => {
  console.log("New worker:", worker.threadId);
});

判断当前线程

ts 复制代码
if (Bun.isMainThread) {
  console.log("主线程");
} else {
  console.log("Worker 线程");
}

四、总结

对比项 Bun 1.2.21 Node.js 24.6.0 提升
3MB 字符串传输 593 ns 242,110 ns ~408 倍
简单对象传输(含 3MB 字符串) 1.26 µs 304 µs ~241 倍
峰值内存 减少 22 倍 --- ---
是否需要改代码 否(自动优化) --- ---

Bun 通过一个优雅的技术洞察------字符串是线程安全的,无需序列化------让 postMessage 实现了数量级的性能飞跃。无论你是构建 API 网关、数据处理管道还是实时应用,Bun 的 Worker 都能带来实打实的性能红利。


📖 参考资料

相关推荐
葫芦和十三2 小时前
图解 MongoDB 12|索引与查询优化地图:一条主线,三个判断轴
后端·mongodb·agent
葫芦和十三8 小时前
图解 MongoDB 11|慢查询排查闭环:从 Profile 到 explain 的分层路径
后端·mongodb·agent
橙子家11 小时前
浏览器缓存之【身份与会话管理】:Cookies 和 Private state tokens
前端
葫芦和十三11 小时前
图解 MongoDB 09|explain 再读:从 queryPlanner 到 executionStats
后端·mongodb·agent
To_OC11 小时前
LC 49 字母异位词分组:想到哈希表很简单,选对 key 才是精髓
javascript·算法·leetcode
葫芦和十三11 小时前
图解 MongoDB 10|覆盖查询:让索引把活干完,根本不用回表
后端·mongodb·agent
最新资讯动态12 小时前
HDC 2026 | 对话鲸鸿动能:存量时代,品牌如何夺回营销“主动权”?
前端
最新资讯动态12 小时前
游戏出海,从产品走向体系
前端
最新资讯动态12 小时前
20人团队跑出百万DAU、大厂也来抢量:谁在鸿蒙生态跑出加速度
前端