文章出处: 【内存优化】Lua配置表导出优化
前言
随着游戏的开发,项目的配置表数据越来越多,占用的内存越来越;配置表占用太大就会影响游戏加载速度,游戏流畅度的每一毫秒都是我们的必争之路。
如果配置文件中的数据过大,或着是有冗余的无用数据,那么势必会导致输出的Lua文件过大,这将严重影响加载速度和增大内存占用量。
我们项目采用的配置表存储方式是Lua表格,策划配表用excel配置,然后使用网上的开源工具excel2lua导出Lua表格,业务使用的时候直接import表格就好了。
在观察了导出的Lua表格后,我整理出一堆待优化的问题:
- 默认值提取
- 字段名优化
- 客户端、服务端表格分离
搞清楚了数据冗余的原因,我们就可以制定优化方案:
1. 默认值提取:优化效果显著
通过观察可以发现其中有部分字段很容易重复,这些字段通常为枚举或者有固定的分类,只有几个不同的值,然而配置表中每个item都需要为这些内容创建一个字段。
我们的优化方案是,利用Lua的特性–原表(metatable),建一张defaultValues表,在导出的时候,选取出现次数最多的值作为每个字段的原表,存到defaultValues,然后剔除每行中与默认值相同的字段,从而节省内存。
优点:一般来说配置表文件优化后只有之前不到一半的大小,大量的重复数据被优化掉了,极大地提升了加载时间和内存占用。
缺点:可读性变差了、然后每行都加了个原表,table变多了,然后访问效率变差了,每次访问如果没找到都要再找一次原表
local defaultValues = {
powerExpendReduce = 264,
magicDefenseIgnored = 27,
stiffnessResistance = 14,
bloodTransferLv = 2,
damageIncrease = 95,
phyShield = 15,
magicDefense = 8000,
}
local ABILITY = {
[101] = { bloodTransferLv = 8000, damageIncrease = 9000, phyShield = 1000, magicDefense = 1864, },
[102] = { magicDefenseIgnored = 33,},
[103] = { magicDefenseIgnored = 36, bloodTransferLv = 7, damageIncrease = 185,},
……
}
do
local base = {
__index = defaultValues, --基类,默认值存取
__newindex = function()
--禁止写入新的键值
error("Attempt to modify read-only table")
end
}
for k, v in pairs(ABILITY) do
setmetatable(v, base)
end
base.__metatable = false --不让外面获取到元表,防止被无意修改
end
2. 字段名优化:内存优化效果显著
热心网友给我提出这种优化,但是这种类似CSV存储格式,可想而知,内存肯定可以降低很多,我简单实现了一下,比对了一下内存,差别还是很大的!!!
但是这种存储格式和默认值方案冲突,只能取舍,而且可读性比较差。现在项目已经做完了,下个项目可以试一下这种方案。
local KeyMap = {
powerExpendReduce = 1,
magicDefenseIgnored = 2,
stiffnessResistance = 3,
bloodTransferLv = 4,
damageIncrease = 5,
phyShield = 6,
magicDefense = 7,
}
local ABILITY = {
[101] = { 264, 27, 14, 8000, 9000, 1000, 1864, },
[102] = { 264, 33, 14, 2, 95, 15, 8000,},
[103] = { 264, 36, 14, 7, 185, 15, 8000,},
……
}
do
local base = {
__index = function(table,key)
local keyIndex = KeyMap[key]
if not keyIndex then
print("key not found: ",key)
return nil
end
return table[keyIndex]
end, --基类,默认值存取
__newindex = function()
--禁止写入新的键值
error("Attempt to modify read-only table")
end
}
for k, v in pairs(ABILITY) do
setmetatable(v, base)
end
base.__metatable = false --不让外面获取到元表,防止被无意修改
end
3. 客户端、服务端表格分离
服务器对于这么点内存,根本不会在意,但是客户端大不一样,是个精打细算的好媳妇,每1KB都要计较的,所以,我们设计一个参数来控制配置表导出对象,C:客户端、S:服务端,在和策划设计表的时候,告知哪些是客户端需要的参数,导出的时候,按C、S导出2张表。
本文中使用的配置表优化工具源码已经放在github,需要的朋友可以自取
链接: https://github.com/Aver58/Tools
Todo:带元表对象的遍历
这样做有个缺点就是,如果是迭代器去遍历的话,会少字段
云风有篇文章讲了这个:正确的序列化 Lua 中带元表的对象
Lua 5.2 之后的版本,约定了在元表中可以给出一个__pairs方法可以用这个元方法实现元表的访问。lua中 table 重构index/pairs元方法优化table内存占用
参考
使用元表优化 Lua 配置文件
Lua配置表存储优化方案 - UWA Blog
Lua元表源码学习一下:lua中metatable源码分析总结_boyhailong的专栏-CSDN博客