iT邦幫忙

2026 iThome 鐵人賽

DAY 5
1

D4 講排序時從欄式轉成列式,今天比的是兩套執行模型:向量化執行(DataFusion、Comet),對上 Spark 的 whole-stage codegen。

先用加工線想這件事

想像 filter → project → sum 是加工線上的三個站
https://ithelp.ithome.com.tw/upload/images/20260821/20183544MXyKfme32a.png

  1. 做法 A:三個工人分站
    第一站把整箱資料的不合格品挑掉,丟進一個中間箱
    第二站再從中間箱拿出來,做欄位加減,成品丟到下一個中間箱
    第三站再拿出來加總,每兩站之間都要一個中間箱。

  2. 做法 B:一個工人一氣呵成
    一個工人拿一件東西,從第一步做到最後一步,中間不放下
    做完這一件,才回去拿下一件
    整條路上都在他手裡

  • 做法 A 就是向量化執行(DataFusion、Comet 走這條),中間箱在電腦裡叫 buffer,是實際佔一塊記憶體
  • 做法 B 是 Spark 的 WSCG(whole-stage code generation,把整段編譯成一個大迴圈),工人手上對應到 CPU 內部速度最快、只放得下少少東西的存放格:暫存器(register)

差別不是哪個有用 SIMD、也不是哪個絕對比較快,差別只有一件事:上一個運算子算出來的中間結果,要不要先寫到一個中間箱、下一個運算子才拿得到

寫進中間箱這件事的術語叫物化(materialize),將抽象的查詢結果、計算過程或圖,實際轉換並以實體檔案、實體資料表的型態儲存在硬碟或記憶體中的技術

codegen 不是向量化

兩件事容易混成同一個詞,差在一次動多少列
沒有 WSCG 之前,JVM 上每一列都要爬過一串算子,每一層一次虛擬呼叫:

scan.next()  →  filter  →  project  →  agg
     ↑ 每一列都把這條鏈走完

WSCG 把這串寫進同一個 for 迴圈,判斷跟加減直接攤在迴圈裡,不再一層層 next()

for (int i = 0; i < n; i++) {
    if (age[i] > 20) {
        sum += salary[i];
    }
}

注意 for 還是 i++,一次一列,但是向量化一次吃的是一批,中間結果放進 buffer,把呼叫鏈壓成一個迴圈,沒有變成一次處理一批

選擇時機

為什麼 A 要中間箱?

因為每個運算子典型上只認得一種輸入:RecordBatch
第一站算完,要變成第二站的輸入,中間就必須有一塊 buffer 把整批擺好。

三個運算子,兩次「寫進 buffer 再讀出來」。

為什麼 B 不用?

因為它把三段程式碼合起來變成一個大迴圈:讀一列 → filter 判斷 → 通過就做 project → 結果加到 sum 的累計器上 → 再讀下一列

這一列的中間值走的是區域變數,JIT 理想狀況會把它們留在暫存器,不必先寫成一批 buffer。

a + b 這種中間結果,沒有「先物化再給下一站」這一步,一列走完,才回頭拿下一列

A 什麼時候贏?

三個理由:

  1. SIMD 只在 batch 裡才發揮:CPU 有指令可以一次算 8 個 int32 加法,做法 B 的迴圈一次走一列,SIMD 不是這段的賣點;做法 A 一站處理一整批,能整齊地丟給 SIMD 吃。
  2. 虛擬呼叫成本被攤掉:呼叫一個運算子本來有固定開銷(動態 dispatch),一次處理 8192 列就分成 8192 份。
  3. 程式碼永遠一份:運算子都是預先編譯好的固定程式碼,來什麼查詢都能跑,不用執行前現產生程式碼。

當一批夠寬、SIMD 收益吃得掉那次多寫多讀的成本,A 就贏。

B 什麼時候贏?

因為中間箱不是免費的:

  • 中間結果要寫進記憶體再讀出來,多一次來回。批越大、欄越多,中間箱就越大,大到塞不進 CPU 的 L2 cache,速度就掉一階。
  • 如果 filter 一過只剩幾列,做法 A 還是照 pipeline 跑,SIMD 的整齊優勢用不上,那個中間箱等於白開了。

當中間結果小、pipeline 短、資料還熱在 cache 裡,B 一路握在手裡就贏。

可是 WSCG 自己也會爆

做法 B 聽起來很美,實際有兩個天花板:

  • JVM HotSpot 的 HugeMethodLimit:預設 8000 bytecode,生成的 Java method 超過這個大小,JIT 預設不編譯,改跑解譯器。
  • spark.sql.codegen.maxFields:預設 100,輸出欄位數(含巢狀欄)超過,Spark 就對那一段子樹關掉 WSCG。

明天預告

明天來做個小實驗,驗證 D2 向量化

Reference:


上一篇
Day 04 DataFusion 是欄式引擎,為什麼排序時要轉成列式?
下一篇
Day 06 效能底層煉金:從 Clang 組合語言拆解 Arrow 記憶體與向量化
系列文
1+1+1>3 ~ Spark 與 DataFusion、Comet 效能煉金術 ~6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言