Lua配置表存储优化方案

来源:互联网 发布:centos软件安装目录 编辑:程序博客网 时间:2024/06/06 19:52

       近几年移动端游戏大热,Unity + Lua更是手游行业的标配技术。如何有效利用有限的硬件条件,实现当前高品质、高规格的游戏产品是持久不变的话题。 Lua作为一个极为精简的嵌入型脚本语言,已经广泛地用在了游戏业,Lua的存在一般是两种场合,一种用于实现游戏上层业务逻辑,一种则利用了Lua语言本身灵活简单的数据表达能力而被广大程序员用于数据的存储,也就是常说的配置文件。

       一般来说配置文件的初始来源是策划维护的有着一定格式约束的Excel表格,经由程序员提供的导出工具,把Excel的表格数据导出成为游戏能直接读取使用的Lua源码。

       这些源码文件以Lua table的形式存储与Excel等价的数据,通常可以简单把这个配置表看成是一组2维数组,转换成配置就是一个key(Excel第一列)对应一组子数据(Excel中一行),那么整个配置数据就是一个大表包含着若干小表转换成配置就是一个key(Excel第一列)对应一组子数据(Excel中一行),那么整个配置数据就是一个大表包含着若干小表,如下:

原始配置表:

这里写图片描述


转换成Lua后大致是这样:
local mapsetting = {    [1] = {        name = "map1",        useminimap = "map1000",        show_name = "十里桃林",        cam_pitch = 40,        cam_yaw = 230,        cam_dis = 10,        pkflag_support = 1,        pkflag_punish = 1,        maxnum = 50,        scheduleConf = {},        scriptId = {},        ...    },    [2] = {        name = "map2",        useminimap = "map1000",        show_name = "碧波潭",        cam_pitch = 40,        cam_yaw = 230,        cam_dis = 10,        pkflag_support = 1,        pkflag_punish = 1,        maxnum = 50,        scheduleConf = {},        scriptId = {10002},        ...    },    [3] = {        name = "map3",        useminimap = "map1000",        show_name = "梵音谷",        cam_pitch = 40,        cam_yaw = 230,        cam_dis = 10,        pkflag_support = 1,        pkflag_punish = 1,        maxnum = 50,        scheduleConf = {},        scriptId = {},        ...    }    ...}return mapsetting

       如果配置文件中的数据过大,或着是有冗余的无用数据,那么势必会导致输出的lua文件过大,这将严重影响加载速度和增大内存占用量。本文将介绍一种消除配置文件中冗余数据,达到压缩和优化数据存储的方法。

       介绍该方法前,大家现来看看一般数据冗余在哪些地方由上面的示例图我们可以清晰的看到数据的冗余点:

  • 大量的数据是重复的,或着是代表没有意义的空值(比如0,[]等)
  • 大量的中文字符串是需要游戏后期做本地化处理的
  • 很多复合型数据(子表,数组)内容是一样的

搞清楚了数据冗余的原因,我们就可以制定优化方案:

  • 对于Excel中的一列,出现次数最多的值认定为默认值,然后把它从lua表中剔除掉,然后利用metatable机制实现全局默认值存储
  • 对于中文字符串,替换为一个唯一的id标识,写回到lua表中,读取的时候加上相应的查找替换
  • 对于一些复杂的子表或着数组,做唯一化替换处理,替换后写回到原始数据中
  • 能看出来,上面的操作其实做的都是唯一化处理,所以有个要求就是整个lua表的数据必须是只读的,如果不满足以上条件,一切优化都是错误的。所以在最后我们需要把整个表改为只读以保护数据不被错误篡改

优化后变成了下面这样:
local __rt_1 = {    0}local __rt_2 = {}local __rt_3 = {    10004}...local __rt_34 = {    desc = "@40313",    drop_show = __rt_33,    scriptId = {        10001,        10011    },    shield = 6,    show_name = "@470882",    subtype = 4}...--超出Lua局部变量个数,则需要另外单独存储和引用--createtable是把c中创建表的函数导入到lua中,这样通过创建一个预分配--的表,能有效的避免table re-hash,有效利用内存--下面创建一个预分配919个数组array-part、0个元素的空hash-partlocal __rt = createtable and createtable( 919, 0 ) or {}__rt[1] = {    2861,    1,    46,    8}__rt[2] = {    2861,    1,    55,    12}__rt[3] = {    2861,    1,    50,    7}...local mapsetting = {    {        name = "map1",        scriptId = __rt_2,        shield = 0,        show_name = "@384651",        type = 0,        ...    },    {        desc = "@424100",        music = "map2_r",        name = "map2",        scriptId = {            10002        },        shield = 0,        show_name = "@424100",        ...    },    {        bloomtype = 1,        scriptId = __rt_2,        shield = 0,        show_name = "@491800",        type = 0,        ...    },    {        cam_yaw = 1,        scriptId = __rt_2,        shield = 0,        show_name = "@499116",        type = 0,        ...    }}...--大量的默认值存储在这里local __default_values = {    Belongto = 0,    Instance_Show = 0,    Instance_group = 0,    daily_getcount = 9,    damage_modify = 1,    desc = "@282313",    difficult = 0,    pkflag_support = 1,    scheduleConf = __rt_2,    scriptId = __rt_32,    ...}...do    local base = {        __index = __default_values, --基类,默认值存取        __newindex = function()            --禁止写入新的键值            error( "Attempt to modify read-only table" )        end    }    for k, v in pairs( mapsetting ) do        setmetatable( v, base )    end    base.__metatable = false --不让外面获取到元表,防止被无意修改endreturn mapsetting

       看上去可读性没有原始的那么高了,一些被多处引用的子表已经被替换成了一个变量,这些变量是以local的形式存储在作用域的,由于lua本身的一些限制,一个作用域内能够存放的最大local变量的个数是200个(lparser.c #define MAXVARS 200),所以超过个数限制的多余部分表会被放入一个临时数组中,初始化的时候需要额外的查表,稍微多一些开销,但可以接受。

       一般来说配置表文件优化后只有之前不到一半的大小,大量的重复数据被优化掉了,极大的提升了加载时间和内存占用。


前后文件对比如下:

左右两边分别是处理前后文件大小对比

       最后说下宿主语言环境中,如果需要读取lua表中的子表数据,我们可以把该表的pointer拿出来作为键值,存放于查找表中,这样宿主环境中也能读取到一个唯一的数组,节约内存。


本文中使用的配置表优化工具源码已经放在github,需要的朋友可以自取,喜欢的朋友别忘了给个小星星哟:)

https://github.com/lujian101/LuaTableOptimizer

1 0
原创粉丝点击