51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

序列化 Lua 对象

在项目中由于种种需求经常需要将对象序列化成一个字符串. 由于 Lua 唯一的复合结构是 table, 所以实现起来还是比较简单的. 之前我们的做法是用 Lua 写一个递归函数遍历 table 的键值然后转换成字符串并拼接起来. 然而 Lua 在字符串拼接的过程中会不断地构造字符串对象, 因此这样的实现方式性能较差, 并且会浪费内存, 特别是数据比较大的时候. 一种优化方式是将键值转换的小字符串存到一个 table 里, 最后使用 table.concat 拼接. 不过我想跟进一步, 使用 C 实现它.

C 实现的序列化函数与 Lua 实现的类似, 都是遍历 table, 遇到简单类型就直接转换成字符串, 遇到 table 就递归. 这里我们就可以把键值转换的小字符串存到一个缓冲区里, 然后将缓冲区转换成字符串 push 进栈返回即可. 这比使用 ..table.concat 要快不少. 一开始我直接使用 luaL_Buffer 作缓冲区, 后来发现这玩意儿有坑, 它在扩容的时候往栈里 push 东西, 导致数据不正确. 为此我就自己写一个 buffer 代替 luaL_Buffer. 这里我借鉴了 luaL_Buffer 的设计: 当数据比较小时, 数据直接存在栈里, 不需要向操作系统申请内存; 只有当数据长度超过某个值时才申请内存. 由于项目中大部分序列化操作的数据都比较小, 这个做法可以带来不少优化. 此外我实现的 buffer 实际上是个链表, 扩容的时候只需要追加链表节点即可, 不需要复制数据. 在最后, 只有扩容过的 buffer 才需要将数据复制出来, 否则只需要将头节点数据的指针取出即可.

有时我们对序列化对象的结果没有可读性需求, 这个时候序列化成二进制数据会比较快, 序列化的结果也比较小. 这里我参照了云风大神的项目 cloudwu/lua-serialize. 由于我们的项目没有序列化成 userdata 的需求, 这里我就简化, 并且使用我自己写的 buffer 代替他的 struct write_block. 反序列化时由于不用考虑链表的问题, 也可以简化. 此外我们还需要将序列化的数据在网络中传递, 这就需要考虑整数字节序的问题了. 因此我还加上了字节序转换.

最后粗略测试了下性能, 运行效率大概是 Lua 实现的四倍, 二进制序列化又比文本序列化快一倍以上. 感觉还有优化空间. 完整代码见 luyuhuang/cseri.

赞(0)
未经允许不得转载:工具盒子 » 序列化 Lua 对象