Lua | 每日一练 (3)

发布于:2025-02-23 ⋅ 阅读:(13) ⋅ 点赞:(0)

💢欢迎来到张胤尘的技术站
💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥

Lua | 每日一练 (3)

题目

lua 中的 table 性能优化有哪些技巧?

参考答案

tablelua 的一种数据结构用来创建不同的数据类型,例如:数组、字典。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

本文中总结的优化点可能不全面,如果大家有更好的优化点,也可以同样可以在评论区中分享出来~~ 期待😀

🌺🌺🌺撒花!

如果本文对你有帮助,就点关注或者留个👍
如果您有任何技术问题或者需要更多其他的内容,请随时向我提问。

在这里插入图片描述