iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
佛心分享-SideProject30

Mongory:打造跨語言、高效能的萬用查詢引擎系列 第 29

Day 28:為什麼 Go 需要 native 重寫:cgo overhead 與編譯器演進

  • 分享至 

  • xImage
  •  

Day 27 的數字已經說服筆者:在 Go 的世界,跨界成本是壓倒性的常數項。這篇把「為什麼要 native 重寫」講清楚,回到編譯器與執行模型的基礎,並提出實際的重寫路線與風險控管


cgo overhead:不可忽視的常數項

  • 呼叫成本:每次從 Go 進入 C,都有固定的棧切換、參數處理、調度協調,這些成本與條件是否簡單無關
  • 熱路徑放大:Mongory 的匹配在每筆資料、每個欄位都需要取值與比較,跨界次數一多,常數項就壓過演算法
  • Ruby vs Go 的差異:Ruby 的瓶頸在語言本身執行速度,Go 的瓶頸換成了 cgo 邊界,解法自然不同

編譯器演進與現勢:Go 更像「目的地」

  • 歷程回顧(觀點):Go 雖起於 C 的陰影,但已以 Go 本身編譯 Go,今天的 Go 編譯器與 runtime 對 Go 代碼的優化已非常成熟
  • 實務含義:
    • 對於「簡單、規律、高重複」的資料存取與比較,原生 Go 更容易讓編譯器做內聯、逃逸分析、邊界檢查省略等最佳化
    • 相比之下,cgo 呼叫在最佳化視野之外,難以被 Go 編譯器整體優化

結論:若目標是「最短路徑」,就應讓熱路徑留在 Go 世界裡


重寫路線:保留 DSL/AST,替換執行核心

  • 保留不變:
    • DSL 語法與 JSON 條件結構
    • AST(matcher tree)的節點語意:$eq/$gt/$in/$and/$or/$elemMatch/...
    • explain/trace 的對外接口與輸出風格
  • 替換變動:
    • matcher 執行核心用 native Go 實作
    • 容器取值以 Go 介面抽象,避免反射熱路徑(必要時才反射)
    • regex 與 custom matcher 仍走 adapter,但改為 Go 端實體

遷移策略:先實作最常用的 primitive 與邏輯運算子,確保 80% 場景可用,再逐步補齊長尾


影響評估:功能/效能/維運

  • 功能:
    • 對讀者:API 與語意不變
    • 對維運:兩套執行核心並存期,需確保一致性測試(C 與 Go 執行結果一致)
  • 效能:
    • 期待在簡單與中等複雜度條件下,native Go ≈ 純 Go 的性能檔數級,在複雜條件上,取決於匹配策略(priority/unwrap/early-exit)在 Go 的落地
  • 維運:
    • 減少 cgo 與子模組同步成本,CI 簡化,跨平台一致性更易管理

風險控管:三道保險

  • 一致性測試:
    • 以相同的條件與資料,同時跑 C 版與 Go 版,逐日擴大用例集(fuzz 可加分)
  • A/B 切換:
    • 在開發期保留 cgo 後門(旗標/build tag),必要時快速回退比較
  • 漸進交付:
    • 先釋出 alpha 給早期使用者,蒐集回饋並觀測效能,穩定後再切換為預設

下一步藍圖(示意)

  • v0.x:
    • $eq/$gt/$gte/$lt/$lte/$in/$nin/$and/$or native 化,保留 explain/trace
    • Shallow 取值先走反射,快速達成可用
  • v0.y:
    • 加入欄位 accessor 介面,降低反射在熱路徑的比例
    • $elemMatch/$every 與 regex/native custom 完成
  • v1.0:
    • 整體覆蓋與效能達標,關閉 cgo 路徑(保留維運旗標)

小結

當 Go 編譯器與 runtime 已足夠強大時,最佳解法往往是「讓熱路徑留在 Go」。Mongory-core
但做完了初步橋接後,進行 benchmark 測試
跟 ruby 一樣進行十萬筆資料比對
純 ruby code 速度是 20ms
ruby C mongory 也是 20ms
純 go 來到令人髮指的 2ms
但 go C mongory 卻是 90ms
令人意外的慢,失望
究其根本原因還是 go <-> C 的 cgo 呼叫邊緣成本解決不了
golang 雖然也是從 C lang 起家
但現在已經是用 golang 寫的編譯器來編譯 golang
發展史是
Clang -> bin 編譯器 -> 編譯 golang 寫的編譯器 code -> bin 編譯器
現在跟 Clang 其實幾乎是同一個 level
所以現在要解決,就只能用純 golang 來寫 mongory go
不靠 C core 在 Mongory 的 Ruby 版是英雄,但在 Go 版,最終答案會是 native。保留既有 DSL 與 AST,替換執行核心,才能真正兌現「輕鬆用、跑得快」


上一篇
Day 27:Go bridge Benchmark shock:原因拆解與方向
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言