Skynet实践之「Lua C 模块集成—优先级队列」

本文展示了如何以 C 实现一个通用的"最小堆(Min-Heap)优先队列 并在 Skynet 中通过 Lua C 模块集成使用,从而获得更高的性能与通用性。


一、C 语言模块:cpriorityqueue.c

以下代码演示了一个最小堆 的数据结构(以 runTime 作为关键字),并提供了pushpoppeek 等接口。该模块以 Lua C API 形式导出函数,让 Lua 层可以通过 require "cpriorityqueue" 来使用。

cpp 复制代码
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct {
    double runTime; // 以这个作为排序字段,更通用的话可以改成 sortkey
    int 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;
        // swap
        PQItem 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;
        // swap
        PQItem 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 index
    pq->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 expand
        int newcap = pq->capacity*2;
        PQItem *newarr = (PQItem*)realloc(pq->array, sizeof(PQItem)*(newcap+1));
        if (!newarr) return 0; // fail
        pq->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);
    // userdatum
    PriorityQueue** ud = (PriorityQueue**)lua_newuserdata(L, sizeof(PriorityQueue*));
    *ud = pq;
    // 设置 metatable
    luaL_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 metatable
    luaL_newmetatable(L, "CPriorityQueueMT");
    lua_pushcfunction(L, l_pq_gc);
    lua_setfield(L, -2, "__gc");
    // methods
    lua_newtable(L);
    luaL_setfuncs(L, pq_methods, 0);
    lua_setfield(L, -2, "__index");
    lua_pop(L,1);

    // module function
    lua_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 table
    l_pq_init(L);
    return 1;
}

编译与集成

bash 复制代码
# 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) ...
  1. 需根据 Skynet 或 Lua 版本包含路径做相应修改 (依赖于lua,需要链接lua库进行编译)INCLUDES = -I ../lua
  2. 编译 make clean && make
  3. 将生成的 cpriorityqueue.so 放在 Skynet 可加载模块目录下(通常 luaclib/ ) 例如:lua_cpath = root .. "luaclib/?.so" 在 skynet 启动配置中 也可以进行配置,确保package.cpath 能搜索到它就可以了

二、在 Skynet lua层中集成流程

Lua层包装 --- 之后就可以在lua层直接使用了

cpp 复制代码
-- 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  = 1
    obj._storage = {} -- { [id] = task }
    return obj
end

function PriorityQueue:push(task)
    -- 1) 确保有 runTime
    local runTime = task.runTime
    if not runTime then
        error("task must have a runTime field!")
    end
    -- 2) todo 生成id  可以使用唯一 id 生成算法
    local id = self._nextId
    self._nextId = id + 1

    -- 3) 存储到 storage
    self._storage[id] = task

    -- 4) 调用 C push
    local ok = self._cObj:push(runTime, id)
    return ok
end

function PriorityQueue:peek()
    local id = self._cObj:peek()
    if not id then
        return nil
    end
    local t = self._storage[id]
    return t
end

function PriorityQueue:pop()
    local id = self._cObj:pop()
    if not id then
        return nil
    end
    local t = self._storage[id]
    if t then
        self._storage[id] = nil
        return t
    end
    return nil
end

function PriorityQueue:size()
    return cpriorityqueue.size(self._cObj)
end

return PriorityQueue

三、性能评估

  • C语言实现的堆操作复杂度 O(log n) 不变,具体的性能提升(相较于纯 lua 实现来说)还得进一步压测。

四、总结

通过上述方案,就能在Skynet 项目中方便地使用C语言的最小堆优先队列通用性方面:

  1. 字段扩展:可轻松在 PQItem 中加更多数据(如 id, priority, callbackRef);

  2. 不止Skynet:只要编译通过并符合Lua C API要求,就可在任何Lua环境中 require。

  3. 使用 :同Lua封装 push/pop/peek/size 即可,类似table 操作,但性能更优

这样,就完成了"以C编写优先队列 并集成到Skynet "的详细流程,一些性能热点可以如此实现,以求更好的性能。

相关推荐
落羽的落羽23 分钟前
【落羽的落羽 数据结构篇】顺序表
c语言·数据结构
IIIIIIlllii1 小时前
C语言练习(28)
c语言·算法
狄加山6752 小时前
C语言基础4
java·c语言·开发语言
人才程序员3 小时前
【PySide6快速入门】QFileDialog 文件选择对话框
c语言·c++·qt·microsoft·pyside6·python3.11
siy23338 小时前
[c语言日寄]assert函数功能详解
c语言·开发语言·笔记·学习·算法
_GR11 小时前
2015年蓝桥杯第六届C&C++大学B组真题及代码
c语言·数据结构·c++·算法·贪心算法·蓝桥杯·动态规划
不悔哥13 小时前
使用openwrt搭建ipsec隧道
linux·c语言·网络·tcp/ip·智能路由器·openwrt
憨猪在度假13 小时前
QT交叉编译环境搭建(Cmake和qmake)
linux·c语言·开发语言·c++·qt
Static_Xiao16 小时前
C语言导航 8.*自定义类型
c语言·数据结构·算法
四念处茫茫18 小时前
【C语言系列】深入理解指针(4)
c语言·开发语言·visual studio