350 個測試、32 MiB 展開碼:深入排查 #[sqlx::test] 的重建時間爆炸問題
kobzol.dev · 2026-06-21
Rust 開發者 Kobzol 於 2026 年 6 月 21 日在個人技術部落格揭露了一個困擾大型測試套件的隱性效能瓶頸:當專案同時使用約 350 個 #[sqlx::test] 標注時,每次修改單一檔案後的重建時間會暴增至 7.3 秒,且 cargo expand 產生的展開程式碼高達 32 MiB。問題的根源在於 sqlx 巨集的程式碼生成機制——每個測試函式都會獨立內嵌完整的遷移腳本(migration),導致編譯器在鏈結前必須處理大量重複的中間表示。
問題如何被發現
該開發者的專案包含約 30 支資料庫遷移腳本與 350 個 #[sqlx::test] 測試。觸碰任何一個檔案後執行 cargo test --no-run,僅重新編譯步驟就耗費約 7.3 秒;相較之下,若將遷移腳本數量縮減為一支,同樣動作只需 5 秒。展開輸出從 5 MiB 膨脹至 32 MiB,約為 6.4 倍,重建時間增加約 46%。這些數字直指 sqlx 巨集展開後的程式碼體積是效能瓶頸的主因,而非業務邏輯本身。
使用 cargo expand 檢視產生的程式碼後可以發現,#[sqlx::test] 在每個測試函式的展開結果中都各自呼叫了 sqlx::migrate!(),這表示每個測試都會在編譯期完整內嵌一份遷移目錄的路徑解析與字串資料。當測試數量達到數百個時,相同的遷移內容就被複製了數百份進入同一個 crate 的中間表示,rustc 需要解析、最佳化並最終丟棄這些重複資訊,造成大量不必要的計算。
既有繞路方案與其缺點
sqlx 早已提供一個手動繞路方式:在 crate 中定義一個靜態的 Migrator,再於每個測試明確引用它:
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!();
#[sqlx::test(migrator = "crate::MIGRATOR")]
async fn my_test(pool: PgPool) {
// ...
}
採用這個方式後,所有測試共享同一份遷移物件,展開程式碼驟降回 5 MiB,重建時間也回到 5 秒左右。然而這個繞路方案的缺點顯而易見:每一個測試函式都必須手動標注 migrator = "crate::MIGRATOR",一旦有人新增測試時忘記加上這個屬性,效能退化就悄悄重現,且難以察覺。
提案:sqlx.toml 的預設遷移器設定
Kobzol 在 sqlx 的 Issue #4318 中提出根本解法:在 sqlx.toml 設定檔中新增 [test] 段落,允許開發者設定預設的遷移器路徑:
[test]
default_migrator = "crate::MIGRATOR"
如此一來,凡是沒有明確指定 migrator 的 #[sqlx::test] 都會自動套用這個預設值,既維持向後相容(opt-in 設定),又消除了每個測試都需手動維護的負擔。實作層面需修改 expand_advanced 函式,在使用者未指定遷移器時讀取設定並替換預設行為。
這個案例清楚示範了 Rust 程式碼生成的一個常見陷阱:過程式巨集若在每次展開時都各自內嵌龐大的常數資料,會使同一 crate 的編譯中間表示快速膨脹。對於測試數量龐大的 Rust 後端服務,在尚未合入修正前,建議先在 crate 層級集中定義靜態 Migrator 並明確引用,以換取顯著的重建時間改善。
原始來源:kobzol.dev;相關 issue:launchbadge/sqlx #4318;文件 PR:launchbadge/sqlx #4320
OCaml 5.5.0 正式釋出:模組相依函式、可重定位編譯器,與約 60 個標準函式庫新增項
discuss.ocaml.org · 2026-06-19
OCaml 核心團隊於 2026 年 6 月 19 日正式發佈 OCaml 5.5.0,這是繼 OCaml 5 引入多域平行(multicore)架構以來持續演進的一個重要版本。此版本帶來語言層面的型別系統擴充、標準函式庫大規模增補、執行期效能改善,以及大幅降低 Windows 原生相依的系統層變動。
語言新特性:模組相依函式與更高階多型
本次最受矚目的語言特性是模組相依函式(Module-Dependent Functions),允許開發者直接在函式型別簽名中使用模組作為參數,形式為 (module M : S) -> t[M]。這是一種輕量級的函子(functor)應用,讓過去需要以 first-class modules 繞路處理的場景,現在可以用更直接的型別標注表達,同時保持型別安全。
另一項改進是對高階多型函式參數(Polymorphic Functions as Arguments)的直接支援,開發者可以用明確型別標注撰寫 let apply_map (map: 'a 'b. ('a -> 'b) -> 'a list -> 'b list),不再需要依賴 type 記錄欄位的繞路手法。廣義局部定義(Generalized Local Definitions)也在此版本擴充:let type t = ... 現在可以出現在局部結構中,與已有的 let module、let exception、let open 並列使用。
型別系統層面還新增了外部型別(External Types)語法:type int_gmp = external "mpz_t",可明確區分來自外部 C 函式庫的不透明型別(opaque type)與 OCaml 自身的抽象型別,對 FFI 整合的型別安全性有所提升。需注意,這也是本版本的一個破壞性變更:模組中的抽象型別不再能夠被可驗證地互相區分,現有依賴此語意的程式碼需要審查。
標準函式庫:約 60 個新增函式
String 模組獲得最多補充,新增 split_first、split_last、split_all、replace_first、replace_last、replace_all、includes、find_first_index、find_last_index 等函式,底層採用高效的雙向字串匹配演算法(2-way string matching)。這批新增大幅縮短了 OCaml 開發者過去需要引入第三方函式庫才能完成的常見字串操作,對於不想引入外部相依的輕量工具特別有用。
數值模組也有補充,Int、Int32、Int64、Nativeint 均新增地板除(fdiv)、天花板除(cdiv)、歐幾里德除法(ediv),以及 leading_zeros、trailing_zeros、bit_count 等位元運算函式。容器模組方面,List.split_map、Set.is_singleton、Map.is_singleton、Hashtbl.find_and_replace、List.filter_mapi 等也一併加入。
執行期與系統層改善
垃圾回收器新增了閒置階段(GC Idle Phase),在小型堆積(small heap)與程式啟動時改善記憶體 pacing,避免不必要的 GC 週期影響延遲。堆疊管理方面,ARM64、POWER、RISC-V 平台實作了分代堆疊掃描(Generational Stack Scanning),可顯著降低深度呼叫堆疊下的 minor GC 工作量。
Windows 平台的重要變更是移除對 Winpthreads 的相依,改以原生 WinAPI 的並行原語實作,這消除了 Windows 上常見的 DLL 相容性問題,對於在 Windows 部署 OCaml 5 應用的場景意義重大。編譯器本身也實現了完整的可重定位(relocatable)安裝,移動或複製編譯器目錄後無需重新設定,並透過複製(clone)而非重新編譯的方式加速建立新的本地 opam switch。
安裝方式如往常透過 opam:
opam update
opam switch create 5.5.0
原始來源:GitHub Release — ocaml/ocaml 5.5.0;發佈公告:discuss.ocaml.org
Rust Foundation 啟動維護者基金:RFC 3931 建立跨組織資金機制,首位駐留維護者預計數月內到位
Rust 官方部落格 · 2026-06-02
Rust 官方部落格於 2026 年 6 月 2 日宣布正式啟動 Rust Foundation Maintainers Fund(RFMF),這是一個由 Rust Foundation 與 Rust 專案治理層共同運作的集中化資金機制,目標是為 Rust 核心元件的長期維護者提供穩定財務支持。背景是業界逐漸出現企業縮減對 Rust 開源工作的贊助,部分關鍵維護者面臨失去全職維護資金的風險。
治理架構:Foundation 與 Project 的分工
RFMF 的治理哲學在於「Foundation 與 Project 缺一不可」。Foundation 負責財務基礎設施(募款、合規、雇用合約),Project 負責技術決策(誰應該獲得支持、支持哪些工作方向),兩者透過一個由 Leadership Council 任命的「Funding Team」進行聯合決策。這個設計旨在防止資金流向與技術優先順序脫節,同時也避免 Foundation 在沒有技術社群背書的情況下單方面決定誰是「重要」維護者。
具體治理依據來自 RFC 3931,該 RFC 定義了 RFMF 與開源 Rust 專案之間的關係,並建立了 Funding Team 的職責範疇:評估申請、溝通專案成員的財務需求、協調各類資助活動、管理贊助商關係。接受支持的團隊也預期協助維護贊助商關係,包括會議參與與合理的贊助商回饋。
兩個核心計畫
RFMF 涵蓋兩個互補的資助計畫。Project Grants Program(依 RFC 3919)提供針對特定貢獻的適度補助金(stipend),適合已有一定貢獻量的開發者。Maintainer in Residence(MiR)Program 則是本次新推出的計畫,提供長期全額薪酬,讓維護者能全時投入 Rust 核心工作,包括:
- 大規模重構(large-scale refactoring)
- 程式碼審查(code review)
- 解除新功能的阻塞(unblocking new features)
- Issue triage 與指導新貢獻者(mentoring)
MiR 的工作時間由兩部分構成:一部分依循接受支持的團隊(如編譯器團隊、Cargo 團隊)的優先事項,另一部分由維護者自行決定方向。首位 Maintainer in Residence 預計在未來數個月內正式雇用,具體申請流程尚未公開,由 Funding Team 與 Foundation 共同評選。
資金來源與捐款方式
RFMF 的資金完全來自外部捐款,所有款項直接用於支持維護者,不抽取行政費用。個人開發者可透過 GitHub Sponsors 捐款;企業贊助則透過相同渠道或直接聯繫 Rust Foundation(contact@rustfoundation.org)。這個「所有收益直接流向維護者」的承諾,是為了建立捐款者對資金用途的信任,與其他基金會的一般運作模式有所差異。
此舉反映了 Rust 生態系統成熟化過程中一個愈來愈迫切的現實問題:當企業大量採用 Rust 之際,承擔核心維護工作的個人開發者卻常因雇主策略轉向而失去全職投入的條件。RFMF 試圖在開源社群自願貢獻模式與正式雇傭之間建立一條新的中間路徑,其效果仍有待首批 MiR 計畫落地後觀察。
原始來源:Rust 官方部落格;治理 RFC:RFC 3931