Lua开发中的常见优化点

Posted 橙子突然想到

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua开发中的常见优化点相关的知识,希望对你有一定的参考价值。

笔者经历的项目中有使用到Lua进行客户端逻辑的开发,在回顾内容时,大致浏览学习并整理了一些Lua开发中常见的优化点,分享给大家。本篇文章由笔者学习整理得出,如果有错误之处欢迎指出~

  • 自5.0版起,Lua使用了一个基于寄存器的虚拟机。(与CPU 中真实的寄存器并无关联), 每个活动函数都有自己的寄存器, 由于Lua提供了大量的寄存器, 所以Lua在预编译时就能够在寄存器中存储局部变量, 使得在Lua中访问局部变量的速度远快于访问全局变量, 所以将全局变量/全局函数缓存为局部变量再进行使用可以提升代码的运行效率。

  • Lua中的类型table由hash部分与数组部分组, 当往table中插入新的元素但是table size不够时, 将会触发Rehash, 重新收集计算table中的元素并进行扩容以确保元素得以容纳, 所以在应用table时应该尽量避免触发Rehash:

    • 使用元素初始化table而不是对空table逐一赋值, 例如:

       -- 效率更高, 只构造一次, size=3
       local a = {1, 2, 3}
       -- 效率更低, 会触发三次Rehash, size变化: 0->1->2->4
       local a = {}
       a[1] = 1
       a[2] = 2
       a[3] = 3
    • 尽量使用大table代替小table, 或者尽量复用table, 避免频繁的table构造与Rehash, 例如:

       -- 效率更高
       local t = {}
       for i = 1, 100000 do
           t[i] = i
           -- something else
       end
       -- 效率更低
       for i = 1, 100000 do
           local t = {}
           t[i] = i
           -- something else
       end
    • 初始化的写法需要注意:

       -- 识别为数组, 直接初始化为size=3的table
       local t = {1, 2, 3}
       -- 下面这个写法表示lua没有这么智能, 不会识别为数组
       -- 而是会进行三次Rehash, 最终size=4
       local t = {[1] = 1, [2] = 2, [3] = 3}
    • 企图通过置空表项来缩小table占用的内存空间是不现实的, 当空间不足时,table的空间重新计算会在Rehash时进行, 但是对table中某个键值对的值置为nil的行为却绝对不会导致Rehash, 这么做也是有原因的: 如果这种行为会导致Rehash, 那么以下这种写法就不再安全:

       for k, v in pairs(t) do
        -- 如果置空会导致Rehash, 这次遍历就会被破坏, 迭代器不再有效
           t[k] = v
       end

      另外一个原因认为, 对键值对的值赋值时不应该做多余的条件检查(比如检查赋值的值是否为nil然后缩减空间占用), 这种行为会极大的影响赋值效率。一种比较曲线救国的做法是往table中新插入足够多个nil元素, 触发Rehash, 此时table会收回所以nil占用的空间(不推荐这么做, 一方面你不会知道足够多指多少个, 另一方面这个做法比较耗时)。

  • Lua中的字符串拼接行为优化: Lua中的字符串都是内化的, 变量只能持有字符串的引用, 每当产生一个新的字符串时, Lua就会检测该字符串是否已经存在备份, 如果是, 重用拷贝, 如果不是, 进行创建。这使得在Lua中字符串的比较与赋值都因为是基于引用的操作而变得更快, 但是字符串拼接等可能产生新字符串的行为却会变得更慢(某些允许变量直接持有字符串buffer语言也许会使用buff拼接, 而Lua会完整创建一个新串), 所以尽量使用table.concat()来代替频繁的 .. 操作, table.concat()的行为类似于预先开辟一整块buffer, 然后将字符串拷贝到这整块buffer上, 最终只产生一个新串。示例:

     -- 效率更高
     local astrSum = {}
     for line in io.lines() do
         astrSum[#astrSum + 1] = line
     end
     local strSum = table.concat(astrSum, ",")
     -- 效率更低
     local strSum = ""
     for line in io.lines() do
         -- 持续产生新串!!!
         strSum = strSum .. "," .. line
     end
  • table插入元素的速率优化

    往table的数组部分插入一个元素有很多中方法, 比如原生提供的table.insert(t, e)方法。根据LuaWiki上的优化建议 Lua 5.1 Optimization Notes:

    Short inline expressions can be faster than function calls. t[#t+1] = 0 is faster than table.insert(t, 0)

    也有直接使用t[#t + 1] = e的方式去插入元素的, 实际上这两种方式速率相差不是特别的大, 原因在于不管是table.insert内部的实现, 还是#t的实现, 最终都会调用到lj_tab_len去获取t的长度, 这个操作的时间复杂度为O(log n), 当连续插入大量的元素时, 插入操作的时间复杂度就会由O(n)->O(n logn), 所以计算长度的操作对于插入的时间复杂度影响还是比较大的。为了防止调用该操作, 我们可以自己维护数组的长度, 实例如下:

     -- 效率更好
     lcoal t = {}  -- maybe has some elements
     local iCount = 1
     for i = 1, 100000 do
         t[iCount] = i;
         iCount = iCount + 1
     end
     -- 效率更差
     lcoal t = {}  -- maybe has some elements
     for i = 1, 100000 do
         t[#t + 1] = i;
         -- or worse: use fun call
         -- table.insert(t, i)
     end

    更进一步的, 可以提前对Lua table进行空间分配来避免若干次空间扩展。

  • 当Lua用于配合Unity的游戏开发时, 也有一些指的注意和优化的点,下一次笔者会另外整理一篇文章来说一说。


以上是关于Lua开发中的常见优化点的主要内容,如果未能解决你的问题,请参考以下文章

Review代码思考:排行榜同积分按时间排序优化方案 | Lua开发实战

Lua优化:破解全局变量下的使用困局

linux学习:Nginx--常见功能配置片段与优化-06

如何优化C ++代码的以下片段 - 卷中的零交叉

使用 C++ 反转句子中的每个单词需要对我的代码片段进行代码优化

ListView优化的几点建议