Bun 為 JavaScriptCore 加入實驗性共享記憶體執行緒
GitHub · 2026-06-21
2026 年 6 月,Bun 團隊在其 WebKit fork 中提交了一個實驗性 Pull Request(#249),為 JavaScriptCore 引擎加入共享記憶體執行緒(Shared-Memory Threads)支援。這份 PR 目前仍標記為未完成,共涵蓋 151 個提交,是 Bun 對 JavaScript 並發模型的一次大規模重新設計。
背景
JavaScript 傳統的多工模型依賴 Web Workers 與訊息傳遞,每個 worker 都有獨立的堆積(heap),物件必須序列化後才能跨執行緒傳遞。這種設計在資料密集型運算場景下形成明顯瓶頸,開發者往往需要透過 toString()/eval() 複製函式,或手動管理繁瑣的訊息通道。Bun 此次的目標是引入類似 Java、Go、C# 的「共享堆積執行緒」模型,讓函式可以直接在其他 CPU 核心上執行,同時存取相同的物件而無需複製。
這項設計源自 Filip Pizlo 2017 年的論文,JavaScriptCore 本身作為 WebKit 的 JS 引擎被 Bun 採用。Bun 先前已自行添加過執行緒原語,此 PR 則是在引擎層面做出更根本的變動,代價是約六萬行的程式碼差異,並帶來長期的 WebKit 合併維護負擔。
核心改動
新的 API 極為精簡,允許閉包跨越執行緒邊界,並支援異常與完整堆疊追蹤的傳遞:
// 建立執行緒並等待結果(非主執行緒)
const result = new Thread(heavyFunction, arg1, arg2).join();
// 主執行緒必須使用非阻塞版本
const result = await thread.asyncJoin();
在堆積共享策略上,PR 採用單一垃圾回收堆積讓所有執行緒共用,並以 TID 標記的 butterfly 結構追蹤物件所有權;物件在首次被其他執行緒寫入時會自動轉換為分段儲存(segmented storage)。每個物件有獨立的 cell lock 保護關鍵區段,watchpoint 機制則在假設執行緒局部性失效時觸發重新編譯(recompile)。
同步原語方面,PR 新增了 Lock、Condition、ThreadLocal,並將 Atomics.* 擴充至一般物件屬性。Thread.restrict() 可將物件固定於單一執行緒,防止跨執行緒競態。實作分為兩個階段:Phase 1 已在全局鎖(GIL)下完成語意驗證,序列效能損耗約 0.45%;Phase 2 的「ungil」移除全局鎖以達成真正的平行運算,目前約 93 個執行緒測試在實際平行模式下通過。
影響範圍
效能測試結果喜憂參半。在簡化資料結構的平坦基準測試中,16 執行緒下 JavaScript 達到約 Java 效能的 0.89 倍,接近原生水準。然而在含有真實字串、Map、BigInt 的完整基準測試中,則比 Java 慢約 13 倍,主因是 JavaScript 物件模型本身的開銷(BigInt 約佔 40%、字串與 Map 約佔 50%)。
記憶體開銷方面,每個執行緒活躍時約消耗 150KB–1MB,休眠時僅需 30–50KB,遠低於 Workers 的 5–15MB。TSAN 競態檢測報告已從約 10,600 筆清零,Windows 支援尚未啟動,test262 測試套件的執行緒內執行也列於待辦清單。此 PR 目前仍屬實驗性質,但其技術路線清晰,為 JavaScript 執行環境的並發能力描繪出一條嚴謹的演進路徑。
- 單一共享 GC 堆積,物件跨執行緒寫入時自動轉換儲存格式
- 新增
Lock、Condition、ThreadLocal同步原語 Atomics.*擴充支援一般物件屬性- 序列效能損耗 <0.5%,flag-off 模式下二進位完全不變
- 執行緒記憶體開銷比 Workers 低 10–100 倍
把整個網站藏進一個 Favicon
timwehrle.de · 2026-06-21
工程師 Tim Wehrle 近日在個人部落格發表了一篇技術文章(原文),示範如何將一份完整的 HTML 文件編碼進網站的 favicon 圖示,再於瀏覽器端解碼還原。這個構想看似荒誕,背後卻是對 PNG 影像格式與 Canvas API 的一次別出心裁的運用。
背景
Favicon 通常以 ICO 或 PNG 格式儲存,瀏覽器對這類靜態資源幾乎不設任何限制。ICO 格式本身支援多張不同解析度的子圖像,PNG 則有 tEXt、zTXt、iTXt 等 metadata chunk 可用於附帶任意文字。這些鮮少被利用的格式特性,為將任意資料隱藏於視覺資源之中留下了空間。Wehrle 選擇以 PNG favicon 搭配 HTML5 Canvas 作為實作路線,利用像素的 RGB 色版逐位元組儲存文字內容。
核心改動
編碼流程分為三個步驟:首先以 TextEncoder 將 HTML 轉為位元組陣列,並在開頭加入四個位元組記錄內容總長度;接著依序將每個位元組對應至像素的紅(R)、綠(G)、藍(B)色版;最後將像素陣列寫入 PNG 檔案,生成 favicon。一份 208 位元組的 HTML 文件僅需 71 個像素,放入 9×9 的 PNG(81 像素可用)綽綽有餘,儲存效率達 87%。
// 編碼:HTML → 位元組 → 像素
const bytes = new TextEncoder().encode(html);
const header = new Uint8Array(4);
new DataView(header.buffer).setUint32(0, bytes.length);
const payload = new Uint8Array([...header, ...bytes]);
// 每 3 個位元組對應一個像素的 R、G、B 色版
// 解碼:從 Canvas 讀回像素
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
// data[i*4]=R, data[i*4+1]=G, data[i*4+2]=B(忽略 Alpha)
解碼端需要一段 JavaScript 引導程式(bootstrap loader):先將 favicon 載入為 <img> 元素,繪製到 Canvas 後讀取像素值,重建位元組陣列,再以 TextDecoder 還原 HTML 字串並動態插入頁面。favicon 本身並不具備自執行能力,必須搭配這段引導程式才能還原內容,這是此方案最關鍵的限制。
影響範圍
這項技術目前更偏向概念驗證,實際應用場景受到若干制約。每個像素的 Alpha 色版被忽略,等同於損失 25% 的儲存空間;PNG 壓縮在特定像素組合下的行為也需要留意。作者亦指出 PNG metadata chunk 是更直接的替代方案,tEXt chunk 可直接嵌入純文字,無需像素轉換,且不受影像壓縮干擾。
儘管如此,這個實驗展示了瀏覽器既有 API 的一種反直覺用法,也再次提醒開發者:任何二進位格式都是潛在的資料載體。從 ICO 的多子圖像結構到 PNG 的 metadata chunk,圖像格式中隱藏著比大多數人預期更多的彈性空間。
- HTML →
TextEncoder→ 位元組序列 → RGB 像素 → PNG favicon - 四位元組長度標頭,方便解碼時精確截取內容
- 208 位元組 HTML 僅需 71 像素,9×9 PNG 即可容納
- 解碼依賴 Canvas
getImageData(),需要 JavaScript 引導程式 - PNG metadata chunk(tEXt/zTXt/iTXt)可作為更穩定的替代方案
原始來源:timwehrle.de