iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
佛心分享-SideProject30

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

Day 19:關鍵優化 1:pool reset、key cache map 的成效

  • 分享至 

  • xImage
  •  

承接 Day 18 的震撼:初版 C bridge 大約比 plain Ruby 慢 3 倍。今天聚焦兩個「小改動、大成效」的優化:pool->reset 與 key cache map(string_map/symbol_map),並以數據趨勢、回歸驗證與風險控管收束

目標與思路

  • 目標:降低重複配置、減少跨界鍵轉換成本,不改動核心語意與對外 API
  • 思路:
    • 在匹配(match/trace)迴圈完成後,用 scratch_pool->reset 重用 chunk,避免反覆 malloc/free
    • 將常用鍵緩存於 string_map/symbol_map,以 Ruby VALUE 固定化,避免重複產生字串/符號並承擔 GC 壓力

實作切片(來自 mongory-rb C 擴充)

初始化 maps 與標記列表(match 建構期,活在 matcher pool):

wrapper->string_map = mongory_table_new(matcher_pool_base);
wrapper->symbol_map = mongory_table_new(matcher_pool_base);
wrapper->mark_list = mongory_array_new(matcher_pool_base);

匹配後重置 scratch/trace pool(一次匹配一重置):

bool result = mongory_matcher_match(matcher, data_value);
if (trace_pool) {
  mongory_matcher_print_trace(matcher);
  trace_pool->reset(trace_pool);
  mongory_matcher_enable_trace(matcher, trace_pool);
}
scratch_pool->reset(scratch_pool);

key cache:以字串作為鍵,建立一次、重用到老,並加入 mark_list 讓 GC 可見:

mongory_value *v = owner->string_map->get(owner->string_map, (char *)key);
if (v && v->origin) return (VALUE)v->origin;
VALUE s = rb_utf8_str_new_cstr(key);
mongory_value *store = mongory_value_wrap_u(owner->pool, NULL);
store->origin = (void *)s;
owner->string_map->set(owner->string_map, (char *)key, store);
owner->mark_list->push(owner->mark_list, store);
return s;

key cache:以符號作為鍵,流程相似:

mongory_value *v = owner->symbol_map->get(owner->symbol_map, (char *)key);
if (v && v->origin) return (VALUE)v->origin;
VALUE sym = char_key_to_symbol(key, enc);
mongory_value *store = mongory_value_wrap_u(owner->pool, NULL);
store->origin = (void *)sym;
owner->symbol_map->set(owner->symbol_map, (char *)key, store);
owner->mark_list->push(owner->mark_list, store);
return sym;

數據觀察(相對趨勢)

  • 簡單條件(age >= 18):時間明顯下降,配置曲線更平穩
  • 複合條件(age >= 18 OR status == 'active'):因 Field/Composite 重複取值多,下降幅度更明顯
  • Net alive(活物件)不再隨輪次持續上升,表示臨時分配被 reset 消化

小結:兩個優化屬「低風險高報酬」,可單獨上線並各自回退

回歸驗證(Correctness)

  • 結果一致:每個量測情境加入計數比對(plain Ruby、Mongory Ruby、Mongory C 一致)
  • 覆蓋面:
    • 欄位存在/缺失、值型別混雜(Nil/Numeric/String)、空陣列/空字串
    • $in/$nin/$regex$and/$or 等組合,含 early-exit 行為
  • 工具:explaintrace 用於觀測邏輯未被更動,CI 以 10k/100k 資料規模抽樣跑快速驗證

風險與回退(Risk & Rollback)

  • reset 邊界:禁止在 reset 後持有 shallow 值,如需保存,轉為 deep 並放入 matcher pool(Day 17 原則)
  • cache 穩定度:string_map/symbol_map 的內容活在 matcher pool,其 VALUE 已加入 mark_list,避免 GC 收走,若需關閉快取,保留旗標可回退至逐次生成
  • 例外處理:如遇 adapter 例外或 pool 錯誤,維持現行錯誤通道(pool->error)并停止本次匹配

實作指引(落地步驟)

  1. 在 matcher wrapper 初始化期建立 string_map/symbol_map/mark_list,生命週期綁定 matcher pool
  2. match?/trace 結尾呼叫 scratch_pool->reset,trace 使用後 reset,不再使用則 free
  3. 在取值前先查 cache(字串/符號),未命中則建立 VALUE,存回 map,並 push 到 mark_list

與下一步的關係

  • 今日兩項優化解決「重複配置」與「鍵震盪」
  • 但若每輪仍對資料做淺層 deep 轉換,複製成本仍是 O(n)
  • 下一篇將介紹結構性突破:Shallow wrap(O(1) 取值)如何把 C 的讀取路徑直通 Ruby C API,進一步追平/超車 plain Ruby

下一篇預告

  • Day 20:關鍵優化 2——Shallow wrap(O(1) 取值)
    • 重新定義 Hash/Array 的包裝策略
    • 讓 C 以常數時間從 Ruby 取值

專案首頁(Ruby 版)


上一篇
Day 18:Benchmark shock:為什麼還是比 plain Ruby 慢?
下一篇
Day 20:關鍵優化 2:Shallow wrap
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言