Bun 如何将 postMessage(string) 提速 500 倍,远超 NodeJs 🚀🚀🚀

在 JavaScript 中,postMessage 是在多个工作线程之间传递数据的常用方法。在 Bun v1.2.21 中,postMessage(string) 的性能几乎与字符串大小无关。这对于多线程 JavaScript 服务器和命令行工具是一个巨大的改进。

通过避免对已知安全共享的字符串进行序列化,性能提高了多达 500 倍,并且在此基准测试中使用的峰值内存减少了约 22 倍。

字符串大小 Bun 1.2.21 Bun 1.2.20 Node 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

这种优化在你将字符串发送到工作线程时自动生效:

javascript 复制代码
const response = await fetch("https://api.example.com/data");
const json = await response.text();

postMessage(json); // 对于大字符串,现在速度提升了 500 倍

这对于在工作线程之间传递大型 JSON 数据的应用程序特别有用,例如 API 服务器、数据处理管道和实时应用程序。

如下代码所示:

ts 复制代码
async function measureTime() {
  const url = "https://microsoftedge.github.io/Demos/json-dummy-data/5MB.json";

  // 开始计时
  console.time("Total Time");

  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP Error! Status: ${response.status}`);
    }

    // 等待JSON解析完成,但不将其赋值给变量,因为我们不需要使用它
    await response.json();

    console.timeEnd("Total Time");
  } catch (error) {
    // 如果发生错误,则结束计时并打印错误信息
    console.timeEnd("Total Time");
    console.error("An error occurred:", error);
  }
}

measureTime();

首先我们分别使用 NodeJs 和 Bun 的旧版本分别测试一下,最终输出结果如下图所示:

当我们把版本升级到最新之后,速度明显增高:

技术揭秘:JavaScriptCore 的优化

postMessage 通常使用结构化克隆算法(Structured Clone Algorithm)在发送到另一个线程之前序列化数据。这意味着将字符串的每个字节复制到一个新缓冲区,然后在另一端反序列化。

但问题是:在 JavaScriptCore(Bun 使用的引擎)中,字符串已经是线程安全的引用计数对象。字符串数据在创建后是不可变的,引用计数使用 std::atomic

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

因此,如果字符串已经是线程安全的,为什么在同一进程中的线程之间发送时还要进行序列化呢?([bun.com][1])

寻找快速路径

并非所有字符串都可以安全地共享。我们识别出三种需要序列化的类型:

  1. 原子字符串(Atom strings):线程本地的属性名和符号。

  2. 子字符串(Substrings):指向其他具有复杂生命周期的字符串。

  3. 绳状字符串(Rope strings):通过操作如 "foo" + "bar".slice() 创建的字符串。

对于其他所有字符串,我们可以完全跳过序列化。我们只需要确保在共享之前计算出惰性计算的哈希值(因为这是唯一可变的部分):

cpp 复制代码
WTF::String toCrossThreadShareable(WTF::String& string)
{
    auto* impl = string.impl();

    // 不能共享原子、符号或子字符串
    if (impl->isAtom() || impl->isSymbol() ||
        impl->bufferOwnership() == StringImpl::BufferSubstring)
        return string.isolatedCopy();

    // 在共享之前强制计算哈希
    impl->hash();

    // 防止该线程进行原子化。
    impl->setNeverAtomicize();

    return string;  // 直接共享指针!
}

toCrossThreadShareable 函数通过识别哪些字符串可以安全地共享而无需序列化,实现了性能的显著提升。这种优化在多线程 JavaScript 服务器和命令行工具中尤为重要。理解其背后的原理有助于我们在开发中更高效地处理字符串数据。

快速路径条件

当满足以下条件时,优化将生效:

  • 你正在使用 postMessagestructuredClone

  • 你只发送一个字符串(而不是混合数据)。

  • 字符串不是子字符串、绳状字符串、原子或符号。

  • 你将字符串发送到同一进程中的另一个线程。

  • 字符串长度 ≥ 256 字符。

这涵盖了在工作线程之间发送字符串的极其常见的模式。

总结

通过识别哪些字符串可以安全地共享而无需序列化,Bun 在 postMessage(string) 上实现了显著的性能提升。这种优化对于多线程 JavaScript 服务器和命令行工具尤其有用。

相关推荐
忍者扔飞镖几秒前
欧服加载太慢了,咋整
前端·性能优化
鹏北海11 分钟前
Vue 3 超强二维码识别:多区域/多尺度扫描 + 高级图像处理
前端·javascript·vue.js
Jack莱杰11 分钟前
Math.js封装工具库(解决前端因为浮点数导致计算错误)
javascript
Android疑难杂症12 分钟前
一文讲清鸿蒙网络开发
前端·javascript·harmonyos
爱学习的程序媛14 分钟前
【JavaScript基础】Null类型详解
前端·javascript
前端一课18 分钟前
uniapp之WebView容器原理详解
前端
CryptoRzz21 分钟前
DeepSeek印度股票数据源 Java 对接文档
前端·后端
Java 码农42 分钟前
nodejs + koa-generator 创建后端项目
node.js
网络点点滴1 小时前
watch监视-ref基本类型数据
前端·javascript·vue.js
西洼工作室1 小时前
前端接口安全与性能优化实战
前端·vue.js·安全·axios