為什麼你該試試 Sccache?
Sccache 是由 mozilla 團隊發起的類 ccache 專案,支援 C/CPP, Rust, nvcc 等語言,並將快取儲存在本地或者雲端儲存後端。在 v0.3.3 版本中,Sccache 加入了原生的 Github Action Cache Service 支援;在後續的 v0.4.0-pre.6 版本中,社群對該功能進行了持續的改進,目前已經初步具備了在生產 CI 中應用的能力。
最近我在 PyO3/maturin 的 CI 中引入了 sccache 做了測試,發現它有如下優勢:
- 部署配置更容易:無需指定
shared-key
,不需要操心 GHA 內部的 cache-from/cache-to 邏輯,配置SCCACHE_GHA_ENABLED: "true"
即可 - 支援多種語言:sccache 同時支援快取 C/CPP,Rust,nvcc 多種語言的不同編譯器,以 Rust 為例,配置
RUSTC_WRAPPER: "sccache"
即可 - 大部分場景下更快:sccache 快取的是編譯產物,不需要提前載入全部的快取,也不需要在構建完成後上傳快取內容
- 併發任務友好:sccache 能在多個併發執行的 job/workflow 之間共享快取,不需要等到構建結束
- 無快取衝突:sccache 對每個編譯產物的輸入(引數,環境變數,檔案等)進行 hash 計算,能構建一個全域性的無衝突快取,不會出現快取衝突,不需要額外指定不同的 cache key
- 無供應商鎖定:sccache 基於 opendal 構建,天然支援各種不同的儲存服務,在未來的 CI 演進中,可以無縫切換到 s3/gcs/azlob 等服務中,不依賴 GHA Cache 服務
- 活躍維護中 (圖窮匕見):sccache 目前由作者本人活躍貢獻中,使用中遇到的問題可以直接提交反饋
以下是在 maturin 專案中的測試結果:
Cases | Sccache vs rust-cache (2nd) | Sccache vs rust-cache (3rd) |
---|---|---|
Test (ubuntu-latest, 3.7) | 59.72% | -1.68% |
Test (ubuntu-latest, 3.8) | -4.70% | -8.22% |
Test (ubuntu-latest, 3.9) | 30.72% | 10.81% |
Test (ubuntu-latest, 3.10) | 1.03% | 12.15% |
Test (ubuntu-latest, 3.11) | -10.16% | -29.35% |
Test (ubuntu-latest, pypy3.8) | 18.34% | -3.84% |
Test (ubuntu-latest, pypy3.9) | 5.13% | 22.90% |
Test (macos-latest, 3.7) | 11.87% | 5.65% |
Test (macos-latest, 3.8) | -7.82% | -13.65% |
Test (macos-latest, 3.9) | -17.98% | -45.20% |
Test (macos-latest, 3.10) | -13.20% | -15.38% |
Test (macos-latest, 3.11) | -17.44% | -29.55% |
Test (macos-latest, pypy3.8) | 14.83% | -23.32% |
Test (macos-latest, pypy3.9) | -28.03% | -38.56% |
Test (windows-latest, 3.7) | 30.08% | 24.22% |
Test (windows-latest, 3.8) | 35.11% | 41.14% |
Test (windows-latest, 3.9) | 9.24% | -5.28% |
Test (windows-latest, 3.10) | -8.56% | -15.81% |
Test (windows-latest, 3.11) | -1.39% | -36.49% |
Test (windows-latest, 3.8) | -19.99% | -35.54% |
Test (windows-latest, 3.9) | 18.95% | -8.55% |
表格中對比的是使用 sccache 第二次/第三次執行與使用 rust-cache 時候的差異,加粗的複數條目表示 sccache 比 rust-cache 更快。可以看到,隨著 Cache 命中率的提高,sccache 對比 rust-cache 最大能取得將近 50% 的提升。
試試 Sccache 吧,任何不爽的地方請到 issues 反饋,同時歡迎參與貢獻~
接下來的部分我會首先介紹 Github Action Cache Service 的內部 API 和它的工作原理,然後對比 rust-cache / sccache 實現的差異來說明為什麼 sccache 更好/更快。
Github Action Cache Service 原理
Github Action Cache Service 本質上是一個支援 prefix query 的 immutable 儲存服務,它提供瞭如下非公開的 API:
查詢快取
GET /cache?keys=abc,ab,a&version=v1
keys
: 指定一組逗號分割的查詢 key,返回的結果是相同 version 下字首匹配的最新 keyversion
:指定 cache key 所使用的 namespace
預留快取
POST /caches
- inputs:
{key: <cache_key>, version: <cache_version>}
- outputs:
{cache_id: <cache_id>}
每一組 (key, version)
被預留之後,後續所有攜帶相同 key & version 的請求都會返回 already exists
錯誤,也就是說快取無法覆蓋。請求成功後會返回數字的 cache_id,後續上傳快取和建立快取都會用到它。
上傳快取
PATCH /caches/[cache_id]
上傳具體的快取內容,使用 Content-Range
來標記本次上傳的快取位置。
建立快取
POST /caches/[cache_id]
在所有的快取內容都建立完畢後,可以使用這個 API 來建立快取。只有在這個 API 響應成功後,快取才能被查詢到。
rust-cache 實現
在這套內部 API(合理懷疑是 Azure DevOps 服務提供的)的基礎上,Github 提供了 actions/cache 供使用者使用,而 rust-cache 正是基於 @actions/cache
實現的。
rust-cache 會基於 github job_id,rustc 版本,環境變數,Cargo.lock
等資訊計算出一個 cache key,然後把 ~/.cargo
和 ./target
打包到一起上傳。如果 cache 命中就載入到本地並解壓縮使用,如果 cache miss,則會在 post action 中將 cache 上傳。
rust-cache 的優勢是整個過程只會呼叫一次 GHA Cache 的 API,很少會觸發 rate limit,缺點是一旦 cache 沒有命中,就需要完全從零構建。
sccache 實現
sccache 的 GHA 實現則是完全基於檔案的:
sccache 會根據每一次 rustc 呼叫時傳入的環境變數,二進位制,編譯引數,輸入檔案等資訊計算一個 hash 作為 cache key,如果檔案存在就直接從儲存服務中載入,跳過本次編譯操作,否則就進行編譯並將結果寫入到儲存服務中。這就意味著:
- sccache 不需要處理不同輸入帶來的快取衝突,可以使用總是唯一的 hash 作為 cache key
- sccache 可以在 rustc 編譯時下載需要快取,不需要提前載入全部的內容,也不需要在 Job 完成後全部上傳
- sccache 快取載入邏輯不強依賴於 Cargo.lock 本身,即使在依賴出現大量變化的情況下,也能夠重用快取
- sccache 可以在併發的 Job 之間重用快取,因為大家共享同一個無衝突的儲存空間
不僅如此,sccache 還能夠應用於 c/cpp 等語言的編譯快取,如果專案中同時還存在 gcc/clang 等編譯器的使用,只需要簡單配置即可共享快取。
為了幫助使用者更好地在 Github Action 中使用 Sccache,我們開發了 sccache-action。不過我的 PR 目前還沒有合併,可以先使用我的 fork:
- name: Sccache Setup
# Just for test, come back to upstream after released
uses: Xuanwo/[email protected]
with:
version: "v0.4.0-pre.6"
接下來只需要配置兩個環境變數即可:
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
在每個 Job 的末尾,sccache-action
會輸出本次 Cache 的使用情況:
/opt/hostedtoolcache/sccache/0.4.0-pre.6/x64/sccache --show-stats
Compile requests 1887
Compile requests executed 1035
Cache hits 836
Cache hits (C/C++) 22
Cache hits (Rust) 814
Cache misses 189
Cache misses (Rust) 189
Cache timeouts 0
Cache read errors 0
Forced recaches 0
Cache write errors 0
Compilation failures 10
Cache errors 0
Non-cacheable compilations 0
Non-cacheable calls 852
Non-compilation calls 0
Unsupported compiler calls 0
Average cache write 0.051 s
Average compiler 1.132 s
Average cache read hit 0.000 s
Failed distributed compilations 0
Non-cacheable reasons:
crate-type 521
- 320
unknown source language 11
Cache location ghac, name: sccache-v0.4.0-pre.6, prefix: /sccache/
使用者可以根據這些資訊來調整自己使用 sccache 的策略。
總結
Sccache 以一種全新的方式使用 GHA Cache 來對 Rust 專案進行編譯加速,相比於現存的方案有如下優點:
- 部署配置更容易
- 支援多種語言
- 大部分場景下更快
- 併發任務友好
- 無快取衝突
- 無供應商鎖定
- 活躍維護中
歡迎大家在自己的專案中嘗試和使用 Sccache!
作者:漩渦 @databend
關於 Databend
Databend 是一款開源、彈性、低成本,基於物件儲存也可以做實時分析的新式數倉。期待您的關注,一起探索雲原生數倉解決方案,打造新一代開源 Data Cloud。
-
Databend 文件:https://databend.rs/
-
Wechat:Databend
- 官宣:OpenDAL 成功進入 Apache 孵化器
- Databend query result cache 設計與實現
- 在 KubeSphere 中開啟新一代雲原生數倉 Databend
- Tapdata 和 Databend 數倉資料同步實戰
- 給 Databend 新增 Aggregate 函式 | 函式開發系例二
- 給 Databend 新增 Scalar 函式 | 函式開發系例一
- 利用 Addax 異構遷移資料到 Databend
- 為什麼你該試試 Sccache?
- 圖解一致性模型
- Tokio 中 hang 死所有 worker 的方法
- Sqlite 併發讀寫的演進之路
- Databend 原始碼閱讀系列(二):Query server 啟動,Session 管理及請求處理
- 使用 Databend 助力 MySQL 的資料分析
- 如何貢獻複雜的專案
- 將 Paxos 和 Raft 統一為一個協議: Abstract-paxos
- 一個 C 系程式設計師的 Rust 初體驗
- Databend 內建標量函式開發指南
- Rust, Databend and the Cloud Warehouse(4)Databend 社群如何做測試
- Rust, Databend and the Cloud Warehouse(3)Datafuse 更名為 Databend