工程趣聞 2026 年 8 月 1 日

2026-08-01 — Rust 編譯器月報、Unix daemon 雙 fork 技巧與 DRAM RowHammer 新論文

primary=https://nnethercote.github.io/2026/07/31/how-to-speed-up-the-rust-compiler-in-july-2026.html primary=https://bower.sh/what-the-double-fork primary=https://arxiv.org/abs/2607.28233

兩位數的改善都去哪了?拆解 2026 年 7 月 Rust 編譯器的「摳細節」提速記錄

Nicholas Nethercote's blog · 2026-07-31

Nicholas Nethercote 在個人部落格持續發布「如何讓 Rust 編譯器更快」系列月報,是 rustc 效能圈公認的第一手戰報。這篇 2026-07-31 發布的最新一期彙整了從 2025-12-032026-07-29 期間平均 5.59% 的整體 wall-time 改善,其中 rustdoc 更進步了 37.92%。這說明 rustc 的效能優化已經進入需要一行一行摳的階段,而不是靠一次大改版就能翻倍。

背景

Nethercote 長期負責 Rust 專案的效能剖析與 profiling 基礎建設,他的月報習慣把散落在數十個已合併 PR 裡的效能數字整理成一張總表。這種持續性紀錄讓外部貢獻者能看到「編譯器變快」不是單一事件,而是逐月累積的小刀。這期報告特別把 rustdoc、Clippy、增量編譯(incremental compilation)與新版 trait solver 四塊拆開報告,方便追蹤每個子系統各自的進度。增量編譯指的是 rustc 把編譯過程拆成一個個 query,結果快取到磁碟,下次編譯若輸入沒變就直接複用,避免整個重新算一次。

rustdoc 與 Clippy 的優化

rustdoc(產生 API 文件的工具)這次是大贏家:Noah Lev 的 #159623#159721#159779#159854 減少了 impl(trait 實作)處理的額外開銷;Jakub Beránek 的 #159091 把 rustdoc 加進 PGO 的訓練集,帶來 2.85% 的平均改善;Nethercote 自己的 #157179 則把 impl 排序邏輯改成用較短的文字鍵而非完整 HTML 字串比較,最佳情況下指令數減少超過 6%。PGO(Profile-Guided Optimization)是指先跑一次資料收集去記錄熱路徑,再拿這份剖析結果指導最終編譯器二進位檔的最佳化順序。這幾項疊加後,rustdoc 系列 benchmark 整體 wall-time 減少了 28%。

Clippy(靜態檢查工具)這邊,xmakro 的 #17124#17132 透過合併多個檢查 pass,消除了不必要的虛擬派發呼叫,執行時間降低 10%–30%,分支預測失誤率也降低 20%–80%。Nethercote 在 #157762 提出的替代做法帶來類似的效益。虛擬派發(virtual dispatch)是指透過函式指標間接呼叫,合併多個 pass 可以讓編譯器把這些呼叫內聯,省下間接跳轉的成本。

增量編譯與新版 trait solver

增量編譯這塊有多筆貢獻疊加:

  • Zalathar 的 #153122 改善了磁碟快取數值的提升(promotion)邏輯,最佳情況指令數減少達 6%,#153521 再帶來 2% 改善
  • zetanumbers 的 #154304 調整了 typeck query,最佳情況減少 6%
  • xmakro 的 #157781#158794#159115 針對依賴圖(dep graph)去重複,帶來 5%–10% 的改善

新版 trait solver 的進展最戲劇化:某個 benchmark 在三個月內從 27 秒降到 1 秒以內。這次月報裡,Nethercote 用 DHAT(一款會記錄每一次記憶體配置與複製的堆積剖析工具)在 #160005 中發現 trait solver 對一個 136 位元組的 GoalStalledOn 結構做了過量的 memcpy。他把其中兩個 Vec 欄位換成 ThinVec,把結構縮到 104 位元組,低於 LLVM 128 位元組的 memcpy 門檻,因而改用暫存器搬移取代慢速記憶體拷貝,cargo check 的指令數減少約 40%。

Benchmark本地量測(instruction count)CI 量測
wg-grammar-7.31%-4.8%
deeply-nested-multi-5.77%-4.07%
serde-3.27%-2.03%

AST 層級的位元組級優化

另外幾筆改動則在跟 記憶體佈局 較勁:#158942 從 AST 節點移除一個欄位,帶來不到 1% 的改善;#158720 把 AST 表達式節點從 72 位元組縮到 64 位元組,讓 AST 密集型 benchmark 的 wall-time 改善超過 10%,cache miss 率最多降低 29%;#159266 重整屬性(attribute)的內部表示,帶來不到 1% 的效益。aerooneqq 的 #155678 把部分查詢合併到 HIR 層級,帶來近 3% 的改善。這些數字單看都不起眼,但正是這類逐月累積的位元組級優化,撐起了開頭那 5.59% 的整體改善。

原始來源:How to speed up the Rust compiler in July 2026rust-lang/rust PR #160005


為什麼背景服務要「fork 兩次」?重新拆解 Unix daemon 化的老招 double-fork

bower.sh · 2026-07-31

bower.sh 上這篇短文重新解釋了 Unix 系統寫 daemon 時流傳已久的「double-fork」技巧:為什麼要 fork 兩次、中間再插一個 setsid,才能讓程式徹底脫離終端機的掌控。這是每本系統程式設計教科書都會提到的老招,但很多工程師只背步驟、不懂原理。這篇文章的價值在於把 POSIX 的 session 與 process group 規則講得夠白話,讓步驟背後的「為什麼」講得通。

背景:終端機為什麼會殺死你的程式

從 shell 啟動的一般程式會繼承一個控制終端機(controlling terminal)。只要這個終端機關閉(例如 SSH 斷線)或使用者按下 Ctrl+C,系統就會把 SIGHUPSIGINT 送給前景 process group 裡的所有程式,常駐服務也不例外。如果一個原本該長駐背景的服務,只是因為當初啟動它的那個終端機視窗關掉就被連帶殺死,顯然不是我們要的行為。double-fork 正是為了讓程式徹底斷開這條「連坐」關係而設計的老招。

技術細節:fork、setsid、再 fork 一次

POSIX 定義了一條關鍵規則:只有 session leader 才能取得控制終端機。一個 session 底下可以有多個 process group,而只有其中的前景 process group 會收到終端機產生的訊號。double-fork 就是利用這條規則,刻意製造一個「不可能成為 session leader」的孫子行程,讓它從此與任何終端機都攀不上邊。

pid = fork();          // 第一次 fork
if (pid > 0) exit(0);  // 父行程結束,交給 init/PID 1 收養

setsid();              // 子行程成為新 session 的 leader
                       // 並脫離原本的控制終端機

pid = fork();          // 第二次 fork
if (pid > 0) exit(0);  // session leader 結束

// 孫行程:不是 session leader,永遠無法取得控制終端機
for (fd = 3; fd < 64; fd++) close(fd);
open("/dev/null", O_RDWR); dup2(0,0); dup2(0,1); dup2(0,2);

第一次 fork() 產生的子行程還不是 process group leader,讓後面呼叫 setsid() 能順利成功(setsid() 對 group leader 呼叫會失敗)。子行程呼叫 setsid() 後成為全新 session 的 leader,此時它已經沒有控制終端機;接著再 fork() 一次產生孫行程,因為孫行程不是 session leader,依 POSIX 規定它永遠不會意外取得控制終端機,即使不小心 open() 了一個 tty 裝置也一樣。

實務意義

完成雙重 fork 後,daemon 通常還會把 stdin/stdout/stderr 重導到 /dev/null,並關掉繼承來的其餘檔案描述符,避免佔用資源或洩漏機密輸出。文章也提到,原本的父行程結束後,pending 的孫行程會被 init(PID 1)收養並在結束時負責回收(reap),呼叫端不需要自己 wait()。在 systemd 普及之後,大多數服務直接用 Type=forkingType=simple 交給 systemd 管理生命週期,double-fork 已經不是必需品,但在手寫初始化腳本、嵌入式系統或像文章提到的 zmx/daemonize.zig 這類輕量實作裡,這個技巧仍然是最直接、不依賴任何 service manager 的作法。

原始來源:what the double-fork?


為什麼「拍打」記憶體會讓別的位元跳號?一篇試圖把 RowHammer 與 RowPress 物理模型講清楚的新論文

arXiv · 2026-07-31

一篇由 Onur Mutlu 所屬的 SAFARI 研究群掛名的新論文 arXiv:2607.28233,標題為《Demystifying DRAM Read Disturbance》,試圖填補 RowHammer 與 RowPress 現象「實驗觀察」與「元件層級物理模型」之間的落差。這兩種現象都屬於讀取干擾(read disturbance):不斷存取 DRAM 的某一列(row),會讓沒被直接存取的鄰近列意外翻轉位元。這篇論文不提出新的緩解手段,而是用嚴謹的半導體元件模擬重現實驗現象,找出既有理論模型跟真實晶片對不上的原因。

背景:一顆記憶體格為什麼會被「震」翻位元

DRAM 的每個位元存在一個電容裡,靠周期性 refresh 補充漏掉的電荷來維持資料。當某一列的 wordline 被反覆開啟關閉(activate/precharge),電性耦合會讓鄰近列電容的電荷流失速度加快,如果流失速度超過下一次 refresh 補電的間隔,鄰近列就會在下次讀取時翻位元——這就是 2014 年 Kim 等人在 ISCA 發表的《Flipping Bits in Memory Without Accessing Them》裡首次系統性紀錄的 RowHammer 現象。RowPress 則是同一研究群後來發現的變體:不需要反覆開關,只要把一列的 wordline 長時間維持在開啟狀態,同樣會加速鄰近列漏電,而且製程節點越微縮,這種「壓著不放」的攻擊面就越明顯。

技術細節:模擬要對齊哪些量測指標

論文鎖定三個實驗上可觀察、但既有理論模型解釋不完整的量測指標:

  • 位元翻轉的方向(從 0 翻成 1,或從 1 翻成 0)
  • 位元翻轉的數量
  • 最小啟動門檻 ACmin(讓翻轉發生所需的最少 row activation 次數)

研究團隊用 TCAD(半導體元件層級的物理模擬工具)重建單一記憶體格的物理結構,拿模擬結果去對照真實晶片的實驗數據,藉此找出現有理論模型裡缺漏的物理機制,並指出哪些模擬參數才是決定「模擬結果是否吻合真實晶片行為」的關鍵。過去業界為了設計緩解機制(例如 JEDEC 在 LPDDR4 標準裡定義的 Target Row Refresh,偵測到某列被高頻存取後主動 refresh 其鄰居列),多半仰賴這類理論模型去估算安全的 refresh 間隔;如果模型本身跟真實物理機制有落差,緩解機制留下的安全邊界也會跟著失準。

影響範圍

這篇論文的作者群涵蓋 Haocong Luo、Longda Zhou、Ataberk Olgun、İsmail Emir Yüksel、Nisa Bostanci、Zhigang Ji、Xing Wu 與 Onur Mutlu,延續 SAFARI 研究群長年在 RowHammer/RowPress 領域的量測與建模工作。更準確的元件層級模型對晶片製造商與 JEDEC 這類標準制定組織的意義,在於未來設計 refresh 排程或存取計數器類的緩解電路時,能有更貼近真實物理行為的依據,而不只是延伸一個在舊製程節點上校準過、但可能已經跟不上更微縮製程的模型。隨著 DDR5 及後續世代持續微縮電容尺寸,RowHammer 與 RowPress 的攻擊門檻預期只會越來越低,這正是這類基礎物理建模研究的價值所在。

原始來源:arXiv:2607.28233


End of article
0
Would love your thoughts, please comment.x
()
x