rubyfast — 用 Rust 重寫的 Ruby 效能 Linter,比原版快 162 倍
寫 Ruby 的人大概都認識 fasterer 這個 gem,會幫你找出常見的效能反模式。但專案越長越大之後,fasterer 自己的執行速度反而變成痛點。
rubyfast 是用 Rust 把 fasterer 重寫了一次,19 條偵測規則照搬,但速度差異很有感:2,235 個 Ruby 檔案,原本 34.1 秒,現在 0.21 秒,快了 162 倍。
為什麼要用 Rust 重寫
fasterer 的瓶頸在 Ruby 的 parser 和單執行緒架構。rubyfast 換掉了這兩件事:parser 改用 ruby-prism(Ruby 3.3+ 之後的官方 parser)解析 AST,掃描則交給 rayon 平行跑。順便 Ruby runtime 也不用了,一個 binary 就能執行。
能偵測什麼
rubyfast 支援 19 種效能反模式偵測,以下列出幾個常見的:
| 反模式 | 建議替代 | 可自動修復 |
|---|---|---|
.shuffle.first | .sample | 是 |
.select{}.first | .detect{} | 是 |
.reverse.each | .reverse_each | 是 |
.keys.each | .each_key | 是 |
.map{}.flatten(1) | .flat_map{} | 是 |
.gsub("x","y")(單字元) | .tr("x","y") | 是 |
for x in arr | arr.each | 是 |
.sort { |a,b| ... } | .sort_by | 否 |
rescue NoMethodError | respond_to? | 否 |
| getter/setter 方法 | attr_reader / attr_writer | 否 |
其中 8 種支援 --fix 自動修復。修補完會再 parse 一次確認沒語法錯,才把檔案寫回去。
安裝與使用
# 透過 Cargo 安裝
cargo install rubyfast
# 掃描目前目錄
rubyfast
# 掃描特定路徑
rubyfast path/to/file.rb
rubyfast path/to/directory
# 自動修復
rubyfast --fix .
也有 Docker image 可以直接使用:
docker run --rm -v $(pwd):/app ghcr.io/7a6163/rubyfast:latest輸出格式
三種格式對應不同情境:
# 依檔案分組(預設)
rubyfast --format file .
# 依規則分組,附數量統計
rubyfast --format rule .
# 一行一個 offense,適合 CI / grep / reviewdog
rubyfast --format plain .設定檔
支援 .rubyfast.yml 或 .fasterer.yml,可以停用特定規則或排除路徑:
speedups:
shuffle_first_vs_sample: false
exclude_paths:
- vendor/**
- db/migrate/**
設定檔會自動往上層目錄搜尋,跟 .rubocop.yml 的行為一致。
Inline Disable
跟 RuboCop 類似,可以用註解在程式碼中局部停用規則:
# 單行停用
arr.shuffle.first # rubyfast:disable shuffle_first_vs_sample
# 下一行停用
# rubyfast:disable-next-line shuffle_first_vs_sample
arr.shuffle.first
# 區塊停用
# rubyfast:disable shuffle_first_vs_sample
arr.shuffle.first
# rubyfast:enable shuffle_first_vs_sample
也相容舊版 fasterer:disable 語法。
CI 整合
在 GitHub Actions 中可以搭配 rubyfast-action 使用:
- uses: 7a6163/rubyfast-action@v1
搭配 --format plain 輸出和 reviewdog,可以在 PR 上直接顯示 inline comments。
GitLab CI 也能透過 Docker image 整合。
幾個實作細節
修復是會互相位移的,所以多個 patch 會按 byte offset 反向排序再依序套用,免得後面的修補蓋掉前面的。byte → line 轉換用 binary search 查預先建好的 newline 陣列,O(log n)。掃描是 rayon 的 parallel iterator,每個檔案獨立 parse + 掃描。修復寫回前會再 parse 一次驗證語法。
收尾
Ruby 專案還在用 fasterer 的話,rubyfast 規則和設定檔都相容,可以直接抽換掉。多了自動修復功能。丟進 CI pipeline 也幾乎感覺不到等待時間。