iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0

D21 講過單一表達式有三條路,第三條就是 codegen dispatch。今天不重講原理,講最近 comet 在做的事:把表達式一個一個從 fallback 搬到 dispatcher 上

這件事在 Comet 社群裡過去大半年一條持續的主線,累積下來的改動量不小,記錄一下它長什麼樣子。

要搬的東西分兩種

第一種是根本沒有 serde 的表達式。查詢裡只要出現它,包著它的整個 operator 就 fallback 回 Spark。make_interval 就是這種,以前 Comet 對它完全沒有處理。

第二種比較微妙:有原生實作,但標成 Incompatibletranslateto_csv 屬於這類。它們有 Rust 實作,跑得快,但結果不保證跟 Spark 完全一致,所以預設不啟用,除非使用者自己設 allowIncompatible。實務上大部分人不會去設,所以「有實作」跟「沒實作」對使用者來說效果一樣,都是 fallback。

兩種的終點都一樣:一個表達式沒接住,賠掉整段 native 管線,中間還多一次 columnar to row 的往返。

搬過去之後長什麼樣

改動本身其實很小,serde 混入 CodegenDispatchFallback 就好。之後這個表達式會走 Spark 自己 doGenCode 產生的程式碼,在 Comet 的 native 管線裡對 Arrow vector 直接讀寫。

第一種情況比較單純,從「整段 fallback」變成「留在管線上」,純賺。

第二種情況有意思一點,它用的是 NativeOptInAvailable 這個 pattern:預設走 dispatcher,原生路徑降級成 opt-in

換句話說,預設值的語意被翻轉了。以前預設是「快但可能不一致,所以乾脆不給你用」,現在預設是「正確,而且留在管線上」,想要更快再自己開 allowIncompatible

我覺得這個翻轉才是整個遷移的重點。以前使用者面對的選擇是「正確但慢」跟「快但要自己承擔風險」,而且前者的慢是懸崖式的。現在預設就給你一個又正確又留在管線上的選項,只是沒有原生那麼快。從二選一變成三選一,而且中間那個是預設值。

為什麼 dispatcher 的結果一定對

因為它跑的就是 Spark 那段程式碼,不是 Comet 重刻的版本。

make_interval 是個好例子。這個函式有三個很容易做錯的地方:seconds 參數是 Decimal(18, 6) 要 scale 成微秒並自帶溢位檢查;失敗的時候要看 failOnError 決定拋 ARITHMETIC_OVERFLOW 還是回 NULL,而且要拋跟 Spark 一模一樣的例外;years * 12weeks * 7 兩個乘法各自獨立溢位。原生重刻的話三個都要對才算對,走 dispatcher 就三個全對,不用測。

regex 那一家也一樣。Java 的 java.util.regex 支援 backreference 跟 lookaround,用 C++ RE2 的引擎做不到,所以只要你重刻,語意就一定有缺口。Comet 直接跑 JVM 那顆引擎,這個缺口就不存在。

dispatch 不是終點

最後講清楚一件事,免得誤導。

dispatcher 每個 batch 都要進 JVM 跑一次生成的程式碼,比一顆真正的原生 kernel 慢。Comet 社群對這件事的定位很誠實:dispatch 是低風險的預設值,原生實作才是更好的終局。

make_interval 那個改動的說明裡就直接寫了,如果哪天有人能在原生側同時搞定 decimal scaling 跟兩條 ANSI 路徑,這個 dispatch 版本可以被取代。

所以這場遷移與其說是在追求效能,不如說是在把懸崖填平。以前是「支援就快、不支援就掉下去」,現在中間多了一階。後續再慢慢把那一階換成原生的。

結論

codegen dispatch 的遷移把「Spark 相容性」從重新實作變成直接執行,代價是每個 batch 跑一次 JVM 程式碼。它換來的不是速度,是沒有懸崖的預設值,以及之後逐個用原生 kernel 取代的空間。

版本與時效

本文基於 Comet main 分支(1.1.0-SNAPSHOT)觀察,最後一個穩定版是 1.0.0。這個版本支援 Spark 3.4、3.5、4.0、4.1,並對 4.2 提供實驗性支援;JDK 11 與 Spark 3.4 的支援自 1.0.0 起標記棄用,預計在 1.1.0 移除。

遷移還在進行中,所以讀到這篇的時候,哪些表達式已經搬完、哪些還沒,很可能跟現在不一樣,allowIncompatible 底下剩多少東西也會變。但**「預設值應該給正確,速度讓使用者自己開」這個判斷不會隨版本改變**,它是一個關於預設值該站在哪一邊的取捨,跟 Comet 支援幾個表達式無關。哪天 dispatch 全部被原生 kernel 取代了,這個取捨還是成立的,只是中間那一階變快了而已。

參考資料


上一篇
Day 26 記憶體怎麼跨 runtime 對帳:unified pool 與那些對不準的地方
下一篇
Day 28 C2R:Comet 算完一定要把資料轉回 Spark 的格式
系列文
1+1+1>3 ~ Spark 與 DataFusion、Comet 效能煉金術 ~30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言