💢欢迎来到张胤尘的技术站
💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥
文章目录
Lua | 每日一练 (3)
题目
lua
中的 table
性能优化有哪些技巧?
参考答案
table
是 lua
的一种数据结构用来创建不同的数据类型,例如:数组、字典。table
的能力是非常强大的,但是如果在使用过程中不注意优化细节,可能会性能产生一定的影响。
下面针对使用 table
过程中的常见优化手段进行总结。
减少查找次数
在 lua
中,表查找(尤其是多重嵌套表的查找)可能会因为频繁的键访问而变得相对低效。例如:
local myTable = {
nested = {
value = 10
}
}
-- 低效:每次循环都进行表查找
for i = 1, 1000000 do
local v = myTable.nested.value
-- 使用 v 做一些操作
end
如果某个表的值在循环或其他频繁执行的代码块中被多次访问,可以将其缓存到局部变量中。这样可以避免每次访问时都进行表查找。
修改后的代码如下所示:
local myTable = {
nested = {
value = 10
}
}
-- 高效:将值缓存到局部变量
local cachedValue = myTable.nested.value
for i = 1, 1000000 do
local v = cachedValue
-- 使用 v 做一些操作
end
另外如果需要频繁的访问某个表,可以键表本身缓存到局部变量中,这样可以减少每次访问时的查找路径,例如:
local myTable = {
nested = {
value = 10
}
}
-- 低效:每次访问都从根表开始查找
for i = 1, 1000000 do
local v = myTable.nested.value
-- 使用 v 做一些操作
end
修改后的代码如下所示:
local myTable = {
nested = {
value = 10
}
}
-- 高效:缓存嵌套表的引用
local nestedTable = myTable.nested
for i = 1, 1000000 do
local v = nestedTable.value
-- 使用 v 做一些操作
end
预分配表空间
经常在循环中进行表分配,频繁的表分配会增加垃圾回收的负担,从而影响性能。
因为 lua
中的表分为数组部分和哈希部分,所以预分配表也分为两部分:预分配数组部分、预分配哈希部分。
数组部分(连续整数索引)
如果表主要用于存储连续的整数索引数据(类似数组),可以通过以下方式预分配空间:
local size = 100000 -- 预分配的大小
local t = {}
t[size] = true -- 触发预分配
通过将表的最后一个索引位置赋值,lua
会为表的数组部分分配足够的空间,从而避免后续插入元素时的扩容操作。
哈希部分(非整数索引)
如果表主要用于存储非整数索引(如字符串键),可以通过以下方式预分配哈希部分的空间:
local size = 100000 -- 预分配的大小
local t = {}
for i = 1, size do
t["key" .. i] = true -- 触发哈希部分的预分配
end
减少嵌套深度
嵌套表的深度会影响查找效率。如果可能,尽量减少表的嵌套层级,或者将常用的数据提升到更浅的层级。例如:
-- 原始结构
local myTable = {
level1 = {
level2 = {
value = 10
}
}
}
-- 访问优化前的表
local k = myTable.level1.level2.value
-- 优化:减少嵌套层级
local myTable = {
value = 10,
level1 = {
-- 其他数据
}
}
-- 访问优化后的表
local v = myTable.value
避免表中存在非连续索引
由于表可以同时作为数组(连续索引)也可以作为哈希表(非连续索引),那么当表中同时存在连续索引和非连续索引时,可能会导致性能下降和内存浪费。
首先需要搞明白为什么存在非连续会影响性能?
- 第一,如果表中同时存在连续索引和非连续索引,
lua
会同时维护这两部分,导致内存分配和管理变得更加复杂,增加了内存开销。 - 第二,对于连续索引的数组,
lua
可以通过简单的指针偏移快速访问元素;而对于非连续索引,lua
需要进行哈希查找,这会增加访问时间。 - 第三,非连续索引的表会增加垃圾回收的复杂性,因为
lua
需要同时处理数组部分和哈希部分的内存回收。
使用独立的表
如果需要存储不同类型的数据(数组和哈希表),建议使用两个独立的表,而不是混合在同一个表中。例如:
-- 混合使用
local t = {1, 2, 3}
t["key"] = "value"
-- 使用两个独立的表
local array = {1, 2, 3}
local hash = {key = "value"}
避免稀疏数组
稀疏数组(即存在大量空隙的数组)会导致表的内部结构变得复杂。如果需要使用稀疏数组,建议使用哈希表代替。例如:
-- 稀疏数组
local t = {}
t[1] = 1
t[1000000] = 1000000 -- 导致表内部结构复杂化
-- 使用紧凑的哈希表结构
local t = {}
t["key1"] = 1
t["key2"] = 1000000
清理表中的空隙,优化表的结构
如果表中存在非连续索引,可以通过重新排序或清理空隙来优化表的结构。例如:
local t = {1, 2, nil, 4, 5}
local new_t = {}
for i, v in ipairs(t) do
if v ~= nil then
table.insert(new_t, v)
end
end
t = new_t
常使用元表和元方法优化
元表和元方法可以用于优化面向对象的代码,减少函数调用次数,并通过元方法实现高效的常见操作,例如:
local Vector = {}
function Vector:new(x, y)
local obj = {}
setmetatable(obj, self)
self.__index = self
self.__add = function(a, b)
return Vector:new(a.x + b.x, a.y + b.y)
end
obj.x = x
obj.y = y
return obj
end
local v1 = Vector:new(1, 2)
local v2 = Vector:new(3, 4)
local v3 = v1 + v2
print(v3.x, v3.y)
减少垃圾回收的开销
垃圾回收的频率会影响性能。通过重用表、避免不必要的分配以及合理调整垃圾回收参数,可以减少垃圾回收的开销。例如:
collectgarbage("setpause", 100) -- 设置暂停时间
collectgarbage("setstepmul", 200) -- 设置每次回收的步长
减少全局变量的使用
在 lua
中,全局变量的访问速度比局部变量慢,因为 lua
需要遍历全局环境来进行查找。局部变量则直接存储在栈上,访问速度更快。另外将表定义为局部变量,可以减少全局环境的污染,提高访问速度。例如:
-- 全局表
myGlobalTable = {value = 10}
function globalTableAccess()
for i = 1, 1000000 do
local v = myGlobalTable.value
end
end
-- 局部表
local myLocalTable = {value = 10}
function localTableAccess()
for i = 1, 1000000 do
local v = myLocalTable.value
end
end
本文中总结的优化点可能不全面,如果大家有更好的优化点,也可以同样可以在评论区中分享出来~~ 期待😀
🌺🌺🌺撒花!
如果本文对你有帮助,就点关注或者留个👍
如果您有任何技术问题或者需要更多其他的内容,请随时向我提问。