Uber Go Zero-Growth Stack:靜態預分配堆疊讓 goroutine 省下 10% CPU
Uber Engineering · 2026-05-07
Uber 工程團隊於 2026 年 5 月發表一項 Go 執行期優化技術,透過修改 startingStackSize 與 debug.adaptivestackstart 兩個 Go runtime 內部變數,在特定高吞吐服務上將 runtime.copystack CPU 佔比從 9.77% 壓至 0.79%,等效於整體服務 CPU 節省約 10%。
Go 堆疊增長的成本
Go goroutine 的堆疊初始大小為 2 KB,超過容量時執行期會將整個堆疊複製到一塊兩倍大的新記憶體,並更新所有指標(runtime.copystack)。對大量短生命週期 goroutine 或函式呼叫深度較大的服務,這個複製操作會在 CPU profile 中形成明顯的熱點。
Uber 選擇的解決方向不是改變 Go runtime 的公開 API,而是利用私有連結(private linking)直接覆寫 runtime 內部變數,對 runtime 程式碼本身的改動降到最低。
自動化 profiler 引導調整
手動設定每個服務的堆疊大小不切實際。Uber 建立了一套自動化流水線:
- 全機隊 CPU profiler 收集含堆疊追蹤的 profile 資料
- 自定義分析器讀取 ELF debug symbol,反組譯函式找出
sub rsp, N指令確定堆疊需求 - 取 P99/Max 後取接近的 2 的冪次作為
startingStackSize - 設定以服務、環境、zone、region 為粒度注入
同時停用堆疊收縮(stack shrinking),防止 runtime 在低負載時縮小堆疊,導致高負載時再次觸發複製。
實際效果與記憶體取捨
其中一個核心生產服務,測試後確認 runtime.copystack 佔比從 9.77% 降至 0.79%,整體 CPU 節省約 10%。記憶體代價:16 GB 容器約增加 200 MB 靜態堆疊分配(約 1.25%),在記憶體充裕的場景下可接受。另外 Uber 也對 go.uber.org/yarpc 中間件函式的堆疊需求做了分析,發現其初始需求遠小於預設值,將調整後的參數合入共用庫,讓所有使用此庫的服務受益。
C++20 模組五年後:import boost 面臨的生態系現實
isocpp.org · 2026-05-20
C++20 模組(modules)已列入標準超過五年,但 Boost 等大型 C++ 函式庫的採用進度遠不如預期。Rubén Pérez Hidalgo 在 isocpp.org 分享了一個函式庫開發者第一手遭遇的生態系障礙,探討讓 import boost; 成為現實所需克服的技術與工具鏈挑戰。
生態系的核心問題
C++ 模組的語意與標頭檔(header)根本不同:模組在二進制介面(BMI, Binary Module Interface)層面描述 API,必須先編譯才能被消費。這與標頭檔「文字包含」的模型形成根本衝突。對於 Boost 這樣同時支援多個 C++ 標準版本、跨越數十個子庫的專案,建立一致的模組化路徑需要解決多個問題。
編譯器支援的碎片化是第一道障礙。GCC、Clang、MSVC 對模組的實作各有差異,特別是 module partition 與 module interface unit 的細節行為。Boost 的使用者橫跨所有三大編譯器,任何模組化方案都必須在三個實作上均能正確運作。
建置系統整合
模組引入了建置系統無法忽略的依賴掃描(dependency scanning)步驟:編譯一個翻譯單元(TU)前,必須先確定它匯入哪些模組,確保被匯入的模組已編譯完成。這打破了傳統 Makefile 的並行假設,要求建置系統理解 BMI 的生產與消費關係。CMake 自 3.28 起加入實驗性模組支援(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD),但 Boost 的 CMake 整合仍需大量工作。Bazel 與 Meson 的支援程度各有落差。
遷移路徑與現況
目前業界採用的過渡策略包括:同時維護標頭檔與模組介面(雙模式發佈)、以 Header Unit 作為中間步驟(不修改標頭即可 import)、以及漸進式地將子庫一個一個模組化。Header Unit 的方案最具短期可行性,但長期而言仍需要完整的 module interface unit 才能享受建置時間與 ABI 穩定性的好處。社群共識是:標準庫模組(import std;)的生態成熟將是 Boost 模組化的前置條件。
原始來源:isocpp.org