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

通过避免对我们知道可以在线程之间安全共享的字符串进行序列化,在此基准测试中,其性能**最高可提升 500 倍**,并且**峰值内存使用量减少约 22 倍**。
字符串大小 | Bun 1.2.21 | Bun 1.2.20 | Node 24.6.0 |
---|---|---|---|
11 个字符 | 543 纳秒 | 598 纳秒 | 806 纳秒 |
14 KB | 460 纳秒 | 1,350 纳秒 | 1,220 纳秒 |
3 MB | 593 纳秒 | 326,290 纳秒 | 242,110 纳秒 |
当您在工作线程之间发送字符串时,优化会自动生效。
// Common pattern: sending JSON between workers
const response = await fetch("https://api.example.com/data");
const json = await response.text();
postMessage(json); // Now 500x faster for large strings
这对于在工作线程之间传递大型 JSON 有效负载的应用程序特别有用,例如 API 服务器、数据处理管道和实时应用程序。
我们在 JavaScriptCore 中是如何做到这一点的
想了解技术细节吗?继续阅读,深入了解我们如何优化 WebKit 的 JavaScript 引擎中的字符串处理。
关键洞察
postMessage
通常使用结构化克隆算法在发送数据到另一个线程之前对其进行序列化。这意味着将字符串的每个字节复制到一个新的缓冲区中,然后在另一端反序列化。
但事实是:在 JavaScriptCore(Bun 使用的引擎)中,字符串已经是线程安全的引用计数对象。字符串数据本身在创建后是不可变的,并且引用计数使用 std::atomic
。
class StringImplShape {
std::atomic<unsigned> m_refCount; // Thread-safe!
unsigned m_length; // Immutable
union {
const LChar* m_data8; // Immutable
const char16_t* m_data16; // Immutable
};
mutable unsigned m_hashAndFlags; // The only mutable part
};
那么,既然字符串已经是线程安全的,为什么在同一进程中的线程之间发送字符串时还要对它们进行序列化呢?
寻找快速路径
并非所有字符串都可以安全共享。我们确定了三种需要序列化的类型:
- 原子字符串 - 线程本地属性名称和符号
- 子字符串 - 指向具有复杂生命周期的其他字符串
- Rope 字符串 - 由
"foo" + "bar"
或.slice()
等操作创建
对于其他所有情况,我们可以完全跳过序列化。我们只需要确保在共享之前计算惰性计算的哈希值(因为这是唯一可变的部分)。
WTF::String toCrossThreadShareable(WTF::String& string)
{
auto* impl = string.impl();
// Can't share atoms, symbols, or substrings
if (impl->isAtom() || impl->isSymbol() ||
impl->bufferOwnership() == StringImpl::BufferSubstring)
return string.isolatedCopy();
// Force hash computation before sharing
impl->hash();
// Prevent this thread from atomizing.
impl->setNeverAtomicize();
return string; // Share the pointer directly!
}
快速路径条件
当满足以下条件时,应用优化:
- 您正在使用
postMessage
或structuredClone
- 您只发送字符串(不是混合数据)
- 字符串不是子字符串、rope、原子或符号
- 您正在发送到同一进程中的另一个线程
- 字符串长度 >= 256 个字符
这涵盖了在工作线程之间发送字符串的极其常见的模式,这也是性能提升如此显著的原因。
下一步是什么
我们可以将此优化扩展到包括对象和数组中的字符串值,但对于最常见的工作线程通信模式之一的 500 倍提速已经是一个不错的开始。未来,相同的方法也可能适用于其他不可变数据类型。