iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
佛心分享-SideProject30

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

Day 23:cgo 與 submodule:Makefile/腳本同步與開發體驗

  • 分享至 

  • xImage
  •  

Go 端的第一個現實問題,並不是 API 長什麼樣,而是「讀者 go get 下來能不能直接編、可不可用」。筆者在設計 Mongory 的 Go Bridge 時,先把「子模組/cgo/同步流程」釐清,確保讀者零設定就能起步


問題背景:Go modules × git submodule 的落差

  • Go 的模組系統不會幫讀者自動抓子模組的 C 原始碼,若 mongory-go 只是引用 mongory-core 當 submodule,讀者 go get 往往會失手
  • 交叉編譯下 cgo 設定繁瑣,若再加上外部依賴,體驗會顯著下降

筆者的目標很單純:讓讀者只用 Go 的標準流程就能編譯,把複雜度收斂在維運腳本與 CI 端


策略一:嵌入核心快照,Makefile 同步

維運時同步核心的標頭與來源至 cgo/binding/,讀者端不需要 submodule。關鍵腳本如下:

SYNC_SRC := mongory-core
SYNC_DST := cgo/binding

.PHONY: sync-core clean-core

sync-core:
	@git submodule update --init --recursive
	@mkdir -p $(SYNC_DST)/include $(SYNC_DST)/src
	@rsync -av --delete $(SYNC_SRC)/include/ $(SYNC_DST)/include/
	@rsync -av --delete --prune-empty-dirs \
		--exclude 'test_helper/**' \
		--include '*/' \
		--include '*.c' \
		--include '*.h' \
		--exclude '*' \
		$(SYNC_SRC)/src/ $(SYNC_DST)/src/
	@echo "Synced headers and C sources to $(SYNC_DST)"

clean-core:
	@rm -rf $(SYNC_DST)
	@echo "Cleaned $(SYNC_DST)"
  • sync-core
    • 確保子模組同步(維運者行為,讀者無需介入)
    • 僅拷貝 include/src/.h/.ccgo/binding/
    • 排除 test_helper 等非必要內容,縮小體積

策略二:單一編譯單元,降低 cgo 心智負擔

將所有核心 .c 以單檔匯入,交給 cgo 直接編譯,避免額外的 build system 與參數協調:

// This file aggregates all mongory-core C sources (synced into cgo/binding)
// into a single compilation unit so that cgo can compile automatically.

#include "binding/src/foundations/array.c"
#include "binding/src/foundations/config.c"
#include "binding/src/foundations/error.c"
#include "binding/src/foundations/memory_pool.c"
#include "binding/src/foundations/string_buffer.c"
#include "binding/src/foundations/table.c"
#include "binding/src/foundations/utils.c"
#include "binding/src/foundations/value.c"

#include "binding/src/matchers/array_record_matcher.c"
#include "binding/src/matchers/base_matcher.c"
#include "binding/src/matchers/compare_matcher.c"
#include "binding/src/matchers/composite_matcher.c"
#include "binding/src/matchers/existance_matcher.c"
#include "binding/src/matchers/external_matcher.c"
#include "binding/src/matchers/inclusion_matcher.c"
#include "binding/src/matchers/literal_matcher.c"
#include "binding/src/matchers/matcher_explainable.c"
#include "binding/src/matchers/matcher_traversable.c"
#include "binding/src/matchers/matcher.c"
  • 優點:不需要在 Go 端重建 C 專案的 build graph,cgo 直接看到單一 .c
  • 缺點:大型單元增量編譯較慢,但簡化了分發與維護

封裝層:API 最小可用,邊界清楚

Matcher 封裝(節錄):

func NewMatcher(condition map[string]any, context *any) (*Matcher, error) {
    pool := NewMemoryPool()
    conditionValue := pool.ConditionConvert(condition)
    if conditionValue == nil {
        return nil, errors.New(pool.GetError())
    }
    h := rcgo.NewHandle(context)
    pool.trackHandle(h)
    cpoint := C.mongory_matcher_new(pool.CPoint, conditionValue.CPoint, handleToPtr(h))
    if cpoint == nil {
        return nil, errors.New(pool.GetError())
    }
    return &Matcher{
        CPoint:       cpoint,
        condition:    &condition,
        context:      context,
        pool:         pool,
        scratchPool:  NewMemoryPool(),
        tracePool:    nil,
        traceEnabled: false,
    }, nil
}

Memory pool 的重置同時釋放 cgo.Handle,避免洩漏或懸空:

func NewMemoryPool() *MemoryPool {
    pool := C.mongory_memory_pool_new()
    return &MemoryPool{CPoint: pool, handles: make([]rcgo.Handle, 0)}
}

func (m *MemoryPool) trackHandle(h rcgo.Handle) {
    m.handles = append(m.handles, h)
}

func (m *MemoryPool) Reset() {
    C.go_mongory_memory_pool_reset(m.CPoint)
    for _, h := range m.handles {
        h.Delete()
    }
    m.handles = m.handles[:0]
}

Shallow to_string 的橋接,維持觀測友善:

// Forward declarations for shallow to_string
extern char *go_shallow_array_to_string(void *go_array);
extern char *go_shallow_table_to_string(void *go_table);

static char *cgo_shallow_array_to_string(mongory_value *v, mongory_memory_pool *pool) {
    return go_shallow_array_to_string(v->data.a);
}

static char *cgo_shallow_table_to_string(mongory_value *v, mongory_memory_pool *pool) {
    return go_shallow_table_to_string(v->data.t);
}

static void mongory_value_set_array_to_string(mongory_value *v) {
    v->to_str = cgo_shallow_array_to_string;
}

static void mongory_value_set_table_to_string(mongory_value *v) {
    v->to_str = cgo_shallow_table_to_string;
}

開發流程:同步、編譯、驗證

  • 同步核心:維運者執行 make sync-core,把 mongory-core 的標頭與來源拷貝到 cgo/binding/
  • 編譯:讀者端執行標準 go build ./...,cgo 會自動以 core_all.c 編譯核心程式碼
  • 變更核心:核心更新後重跑 sync-core,必要時 make clean-core 清乾淨後再同步
  • 檢視產物:用 go testexplain/trace 驗證可觀測性是否正常

規範與陷阱:cgo 指標與生命週期

  • 不要把 Go 指標長期存放在 C 端,以 runtime/cgo.Handle 與 pool 生命週期管理
  • pool.Reset/Free 務必釋放 handle,避免洩漏
  • shallow 包裝僅在取值時計算,避免不必要的複製與反射成本

小結

筆者把「子模組與 cgo 的複雜度」收斂在維運腳本與單檔編譯單元,讓讀者在 Go 端只需 go build 就能使用 Mongory 的匹配能力。下一篇,筆者會面對 Go 型別反射與 converter 的設計邊界,談如何延續 shallow/deep/recover 讓效能與易用性同時成立

Go 版)


上一篇
Day 22:Mongory Bridge 的下一站:為什麼選 Go
下一篇
Day 24:Go 型別反射與 data converter 困境:設計邊界
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言