本文展示了 如何以 C 实现一个通用的“最小堆(Min-Heap)优先队列 并在 Skynet 中通过 Lua C 模块集成使用,从而获得更高的性能与通用性。
一、C 语言模块:cpriorityqueue.c
以下代码演示了一个最小堆的数据结构(以 runTime 作为关键字),并提供了push、pop、peek等接口。该模块以 Lua C API 形式导出函数,让 Lua 层可以通过 require "cpriorityqueue" 来使用。
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdlib.h>
#include <stdio.h>typedef struct {double runTime; // 以这个作为排序字段,更通用的话可以改成 sortkeyint id;// 其他字段也可放在这里,如 priority, pointers to lua references, etc.
} PQItem;typedef struct {PQItem *array; // 堆存储数组int size; // 当前元素数量int capacity; // 数组容量
} PriorityQueue;//---------------------------
// 堆操作函数 (C层的私有函数)
//---------------------------// 上浮操作
static void pq_sift_up(PriorityQueue *pq, int idx) {while (idx>1) {int parent = idx/2;if (pq->array[parent].runTime <= pq->array[idx].runTime)break;// swapPQItem tmp = pq->array[parent];pq->array[parent] = pq->array[idx];pq->array[idx] = tmp;idx = parent;}
}// 下沉操作
static void pq_sift_down(PriorityQueue *pq, int idx) {int n = pq->size;while (1) {int left = idx*2;int right= idx*2+1;int smallest = idx;if (left<=n && pq->array[left].runTime < pq->array[smallest].runTime) {smallest = left;}if (right<=n && pq->array[right].runTime < pq->array[smallest].runTime) {smallest = right;}if (smallest==idx) break;// swapPQItem tmp = pq->array[idx];pq->array[idx] = pq->array[smallest];pq->array[smallest] = tmp;idx = smallest;}
}//---------------------------
// PriorityQueue接口
//---------------------------
static PriorityQueue* pq_create(int cap) {PriorityQueue* pq = (PriorityQueue*)malloc(sizeof(PriorityQueue));pq->array = (PQItem*)malloc(sizeof(PQItem)*(cap+1)); // 1-based indexpq->size = 0;pq->capacity = cap;return pq;
}static void pq_free(PriorityQueue *pq) {if (!pq) return;if (pq->array) {free(pq->array);}free(pq);
}static int pq_push(PriorityQueue *pq, double runTime, int id) {if (pq->size >= pq->capacity) {// auto expandint newcap = pq->capacity*2;PQItem *newarr = (PQItem*)realloc(pq->array, sizeof(PQItem)*(newcap+1));if (!newarr) return 0; // failpq->array = newarr;pq->capacity = newcap;}pq->size++;int idx = pq->size;pq->array[idx].runTime = runTime;pq->array[idx].id = id;pq_sift_up(pq, idx);return 1;
}static int pq_pop(PriorityQueue *pq, double *outVal) {if (pq->size==0) {return 0; // empty}// root*outVal = pq->array[1].id;pq->array[1] = pq->array[pq->size];pq->size--;if (pq->size>0) {pq_sift_down(pq, 1);}return 1;
}static int pq_peek(PriorityQueue *pq, double *outVal) {if (pq->size==0) {return 0;}*outVal = pq->array[1].id;return 1;
}//---------------------------
// Lua 接口绑定
//---------------------------static int l_pq_create(lua_State* L) {int cap = luaL_optinteger(L, 1, 16);PriorityQueue* pq = pq_create(cap);// userdatumPriorityQueue** ud = (PriorityQueue**)lua_newuserdata(L, sizeof(PriorityQueue*));*ud = pq;// 设置 metatableluaL_getmetatable(L, "CPriorityQueueMT");lua_setmetatable(L, -2);return 1;
}static int l_pq_gc(lua_State* L) {PriorityQueue** ud = (PriorityQueue**)luaL_checkudata(L, 1, "CPriorityQueueMT");if (*ud) {pq_free(*ud);*ud = NULL;}return 0;
}static PriorityQueue* checkQueue(lua_State* L) {PriorityQueue** ud = (PriorityQueue**)luaL_checkudata(L, 1, "CPriorityQueueMT");luaL_argcheck(L, ud!=NULL && *ud!=NULL, 1, "invalid cpriorityqueue");return *ud;
}static int l_pq_push(lua_State* L) {PriorityQueue* pq = checkQueue(L);double val = luaL_checknumber(L, 2);double id = luaL_checknumber(L, 3);int ret = pq_push(pq, val, id);lua_pushboolean(L, ret);return 1;
}static int l_pq_pop(lua_State* L) {PriorityQueue* pq = checkQueue(L);double outVal;int ret = pq_pop(pq, &outVal);if (!ret) {return 0; // return nil}lua_pushnumber(L, outVal);return 1;
}static int l_pq_peek(lua_State* L) {PriorityQueue* pq = checkQueue(L);double outVal;int ret = pq_peek(pq, &outVal);if (!ret) {return 0;}lua_pushnumber(L, outVal);return 1;
}static int l_pq_size(lua_State* L) {PriorityQueue* pq = checkQueue(L);lua_pushinteger(L, pq->size);return 1;
}//---------------------------
// 模块入口
//---------------------------
static const struct luaL_Reg pq_methods[] = {{"push", l_pq_push},{"pop", l_pq_pop},{"peek", l_pq_peek},{"size", l_pq_size},{NULL, NULL}
};static const luaL_Reg empty_funcs[] = {{NULL, NULL}
};static int l_pq_init(lua_State* L) {// create metatableluaL_newmetatable(L, "CPriorityQueueMT");lua_pushcfunction(L, l_pq_gc);lua_setfield(L, -2, "__gc");// methodslua_newtable(L);luaL_setfuncs(L, pq_methods, 0);lua_setfield(L, -2, "__index");lua_pop(L,1);// module functionlua_pushcfunction(L, l_pq_create);lua_setfield(L, -2, "create");return 1;
}LUALIB_API int luaopen_cpriorityqueue(lua_State* L) {luaL_newlib(L, empty_funcs); // new tablel_pq_init(L);return 1;
}
编译与集成
# 1) 编译器 你也可以改成 gcc
CC = clang
CFLAGS = -O2 -fPIC -Wall
LDFLAGS = -shared -undefined dynamic_lookup# 2) Lua 头文件包含路径 (示例: ../lua)
INCLUDES = -I ../lua# 3) 目标名称、源文件
TARGET = cpriorityqueue.so
SOURCES = cpriorityqueue.c# 4) 默认规则: 编译 .so
all: $(TARGET)$(TARGET): $(SOURCES)$(CC) $(CFLAGS) $(INCLUDES) $(LDFLAGS) -o $@ $^# 如果有更多模块:
# $(TARGET2): $(SOURCES2)
# $(CC) $(CFLAGS) $(INCLUDES) $(LDFLAGS) -o $@ $^clean:rm -f $(TARGET)# rm -f $(TARGET2) ...
- 需根据 Skynet 或 Lua 版本包含路径做相应修改 (依赖于lua,需要链接lua库进行编译)INCLUDES = -I ../lua
- 编译 make clean && make
-
将生成的 cpriorityqueue.so 放在 Skynet 可加载模块目录下(通常 luaclib/ ) 例如:lua_cpath = root .. "luaclib/?.so" 在 skynet 启动配置中 也可以进行配置,确保package.cpath 能搜索到它就可以了
二、在 Skynet lua层中集成流程
Lua层包装 — 之后就可以在lua层直接使用了
-- priority_queue.lua
local cpriorityqueue = require "cpriorityqueue"local PriorityQueue = {}
PriorityQueue.__index = PriorityQueue--------------------------------------------------------------------------------
-- Lua层包装
--------------------------------------------------------------------------------
function PriorityQueue:new(initialCap)local obj = setmetatable({}, self)-- cObj: C端优先队列指针obj._cObj = cpriorityqueue.create(initialCap or 16)obj._nextId = 1obj._storage = {} -- { [id] = task }return obj
endfunction PriorityQueue:push(task)-- 1) 确保有 runTimelocal runTime = task.runTimeif not runTime thenerror("task must have a runTime field!")end-- 2) todo 生成id 可以使用唯一 id 生成算法local id = self._nextIdself._nextId = id + 1-- 3) 存储到 storageself._storage[id] = task-- 4) 调用 C pushlocal ok = self._cObj:push(runTime, id)return ok
endfunction PriorityQueue:peek()local id = self._cObj:peek()if not id thenreturn nilendlocal t = self._storage[id]return t
endfunction PriorityQueue:pop()local id = self._cObj:pop()if not id thenreturn nilendlocal t = self._storage[id]if t thenself._storage[id] = nilreturn tendreturn nil
endfunction PriorityQueue:size()return cpriorityqueue.size(self._cObj)
endreturn PriorityQueue
三、性能评估
-
C语言实现的堆操作复杂度 O(log n) 不变,具体的性能提升(相较于纯 lua 实现来说)还得进一步压测。
四、总结
通过上述方案,就能在Skynet项目中方便地使用C语言的最小堆优先队列。通用性方面:
-
字段扩展:可轻松在 PQItem 中加更多数据(如 id, priority, callbackRef);
-
不止Skynet:只要编译通过并符合Lua C API要求,就可在任何Lua环境中 require。
-
使用:同Lua封装 push/pop/peek/size 即可,类似table操作,但性能更优。
这样,就完成了“以C编写优先队列并集成到Skynet”的详细流程,一些性能热点可以如此实现,以求更好的性能。