iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0

本篇聚焦 Mongory 在跨語言邊界的資料轉換:Ruby 的高階物件,如何在 C Core 中以 mongory_value 表示;又如何在匹配完成後安全地「還原」回宿主語言。筆者將分三段拆解:

  • 責任邊界:converter vs adapter
  • 轉換策略:shallow / deep / recover
  • 零拷貝思路與常見陷阱

名詞釐清

  • Mongory Value(mongory_value):C Core 的通用值封裝,具 type tag、資料 union、函式指標(compto_str)與 origin 指向外部原始值。
  • Converter(轉換器):做「資料形狀」轉換,將宿主語言的值轉為 mongory_value,或反向還原。
  • Adapter(配接器):做「行為」外包,像 Regex 與 Custom Matcher 的實作委派回宿主語言。
  • Shallow / Deep / Recover:三種資料跨界策略,分別對應「包裝」「實體化」「還原」。
  • Memory Pool:一致管理生命週期,避免繁複釋放;轉換過程產生的中間物件也掛在 pool 上。

責任邊界:Converter vs Adapter

  • Converter(資料責任)

    • 決定要不要 materialize(深拷貝)或只做包裝(淺封裝)。
    • 決定 key 形狀(String 或 Symbol)、數值寬度(int64/double)、字串編碼等。
    • 將宿主語言集合(Array/Hash)轉為 C Core 的 mongory_array/mongory_table 或其「淺包裝」。
    • 反向的 recover:把 mongory_value 還原為宿主語言值(通常透過 origin)。
  • Adapter(行為責任)

    • Regex:在 Ruby 透過 Regexp#match?(或等效 API),由 adapter 統一掛給 C Core。
    • Custom Matcher:由宿主語言的 registry 提供 build/match/lookup,C Core 只負責呼叫。
    • 任何會「執行邏輯」的事,都應放在 adapter 層,而非 converter。

這個分工確保:

  • C Core 專注在資料結構、匹配流程與生命週期;
  • 宿主語言保留語意與生態(正規表示式、應用層 DSL)。

轉換策略一:Shallow(淺包裝)

目標:O(1) 封裝,儘可能「零拷貝」。集合型別(Array/Hash)以「代理」方式回到宿主語言取值。

Ruby 橋接的關鍵函式(節錄與簡化):

// 將 Ruby Hash 包成 mongory_table,表面上是 C 結構,實際 get() 回撈 Ruby
mongory_value *rb_mongory_table_wrap(mongory_memory_pool *pool, VALUE rb_hash);

// 將 Ruby Array 包成 mongory_array,get() 走回 Ruby 陣列
mongory_value *rb_mongory_array_wrap(mongory_memory_pool *pool, VALUE rb_array);

// 淺轉換:基本型別直接 wrap,集合以 wrap_table/wrap_array 包裝,並記錄 origin
mongory_value *rb_to_mongory_value_shallow(mongory_memory_pool *pool, VALUE rb_value);

重點:

  • origin 永遠指向 Ruby 端原物件,便於 recover 與 GC 標記。
  • mongory_array.getmongory_table.get 會回撈 Ruby 取值,再「遞迴淺包裝」。
  • 適合「只讀巡覽、多次匹配」的熱路徑,避免大量 materialize。

轉換策略二:Deep(深度實體化)

目標:將集合完整 materialize 成 C Core 結構,以利 CPU 連續存取;適用於多次重用、跨執行緒或需要穩定指標的場景。

Ruby 橋接的關鍵函式:

// 深轉換:陣列逐一遞迴轉成 mongory_value,Hash 以 key/value 轉成 table
mongory_value *rb_to_mongory_value_deep(mongory_memory_pool *pool, VALUE rb_value);

實務筆記:

  • Key cache:Ruby 端為了避免重複產生字串/符號 key,會在 wrapper 內用 string_map/symbol_map 緩存,並以 mark_list 掛住避免 GC 回收。
  • Materialize 的資料記憶體由 pool 管,不需個別 free;匹配結束可整池 reset。
  • 相較 Shallow,Deep 付出一次性拷貝,但之後的 get/遍歷皆在 C 端,CPU cache 友善。

轉換策略三:Recover(還原)

目標:將 C Core 的 mongory_value 還原為宿主語言值,常見於 explain/trace、或作為外部 API 回傳。

Ruby 橋接設計:

// 以 origin 還原 Ruby VALUE(無需重建),Zero-Copy Recover
void *mongory_value_to_ruby(mongory_memory_pool *pool, mongory_value *value);

重點:

  • Recover 應盡量利用 origin,避免重複分配與轉碼。
  • origin 不存在(例如全由 C Core 生成的中間值),才需要建立新宿主值。

零拷貝思路與實戰

  • 基本原則

    • Shallow 包裝:集合不搬動資料、只做 O(1) 包裝,get() 時才遞迴包裝內部元素。
    • Recover 優先 origin:將 mongory_value.origin 當作回程的橋梁。
    • Regex/Pointer/Unsupported:以 Ruby VALUE 或 void* 指向外部實體,生命週期由宿主語言控管。
  • Ruby 端技巧

    • origin 全線維護:任何包裝出的 mongory_value 都寫回 origin,以便 GC 標記與 Recover。
    • mark_list:將 cache 與重要 Value 掛入陣列,讓 Ruby GC 能被標記到。
    • to_str 覆寫:需要時以宿主側 inspect/String() 提供可觀測字串(避免 C 側重建)。

何時選擇 Shallow vs Deep

  • 建議使用 Shallow:

    • 單次或少次匹配;
    • 大資料、深巢狀結構,Materialize 成本過高;
    • 需要頻繁返回宿主語言(例如 Regex/Custom Matcher 頻繁觸發)。
  • 建議使用 Deep:

    • 多次重複匹配同一批資料;
    • 跨執行緒/任務重用(避免回到宿主語言取值的同步成本);
    • 關鍵熱路徑需最佳化 CPU cache 局部性。

常見陷阱清單(必讀)

  • 生命週期錯配

    • Shallow 包裝下,origin 指向宿主語言值。若宿主值被釋放或移動,C 側不得持久保存指標。解法:確保匹配於短生命週期 pool 內完成;或 Deep 轉換。
  • 字串與編碼

    • Ruby 的字串可能是多位元編碼,C 側預期 char* 僅為觀察用途,不應修改。必要時 Deep 轉換並由 pool 複製一份。
  • Key 形狀不一致

    • Ruby Hash 可同時有 Symbol 與 String key。Converter 需統一策略(優先 Symbol 或 String)並做 cache,否則將導致查找 miss。
  • Adapter 與 Converter 邊界混淆

    • 不要在 Converter 做「邏輯判斷/執行」。Regex、Custom Matcher 一律走 Adapter。

實作對照:Ruby 橋接(摘錄要點)

// 1) 基本型別:直接 wrap,並回填 origin
static mongory_value *rb_to_mongory_value_primitive(mongory_memory_pool *pool, VALUE rb_value);

// 2) Shallow:集合以 wrapper(rb_mongory_array_t / rb_mongory_table_t)回撈 Ruby 值
mongory_value *rb_to_mongory_value_shallow(mongory_memory_pool *pool, VALUE rb_value);

// 3) Deep:完整 materialize;Hash 走 foreach 建立 table
mongory_value *rb_to_mongory_value_deep(mongory_memory_pool *pool, VALUE rb_value);

// 4) Recover:優先使用 origin 回傳 Ruby VALUE
void *mongory_value_to_ruby(mongory_memory_pool *pool, mongory_value *value);

觀察性與除錯:to_str 與 explain/trace

  • mongory_value.to_str 可由宿主語言注入(Ruby 以 inspect),避免 C 側自行拼字串。
  • explain/trace 走 scratch/trace pool 輕量分配,結束即 reset;不污染主資料池。
  • 若遇到型別不符或轉換失敗,優先在宿主語言丟出語義清晰的錯誤(Ruby TypeError)。

小抄(Cheat Sheet)

  • 一句話原則:資料交給 Converter,行為交給 Adapter。
  • Shallow 優先;判斷熱點再局部 Deep。
  • Recover 優先 origin,避免重建。
  • Pool 要分清:matcher pool、scratch pool、trace pool,避免交叉汙染。
  • Ruby:記得 mark_list 掛住 cache。

實戰範例

# Ruby 端:建構 CMatcher 時,condition 走 deep(穩定 AST),data 走 shallow(零拷貝)
matcher = Mongory::CMatcher.new({ age: { "$gt" => 30 } })

data = { name: "Alice", age: 31 }
matcher.match?(data)   # data 以 shallow 包裝;內部取值回撈 Ruby

matcher.enable_trace
matcher.trace(data)    # 掛上 trace_pool,輸出觀測資訊
matcher.print_trace

結尾

本篇把 VALUE↔C value 串接中的三件事講清楚:Converter 決定資料形狀、Adapter 承接行為;Shallow/Deep/Recover 三種策略互補以追求零拷貝;生命週期由 Memory Pool 一致管理。這套組合讓 Mongory 在 Ruby 與 C 之間獲得「高效」與「可觀測性」的平衡

下一篇將進入 GC × Memory Pool 的生命週期協作,拆解 extern_ctx 與外部物件壽命管理。

專案首頁(Ruby 版)


上一篇
Day 15:Ruby C 擴充與 submodule
下一篇
Day 17:GC × memory pool 生命週期管理
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言