Lua个人学习记录

一.如何写Lua代码

1).编写高性能的Lua代码

以下是对上面文章的摘录和总结

这篇文章是基于Lua语言的创造者 Roberto Ierusalimschy 在 Lua Programming Gems 中的 Lua Performance Tips 翻译改写而来

1.使用local

在写Lua代码时,应该尽量使用local变量。

在代码运行前,Lua会把源码预编译成一种中间码,类似于Java的虚拟机。这种格式然后会通过C的解释器进行解释,整个过程其实就是通过一个while循环,里面有很多的switch…case语句,一个case对应一条指令来解析。

自Lua 5.0之后,Lua采用了一种类似于寄存器的虚拟机模式。Lua用栈来储存其寄存器。每一个活动的函数,Lua都会其分配一个栈,这个栈用来储存函数里的活动记录。每一个函数的栈都可以储存至多250个寄存器,因为栈的长度是用8个比特表示的。

一个活动的函数 —> 一个栈 —> 最多250个寄存器(8byte —> 00000000)

有了这么多的寄存器,Lua的预编译器能把所有的local变量储存在其中。这就使得Lua在获取local变量时其效率十分的高。

2.表table

Lua的表分为两个部分:数组array部分和哈希hash部分。数组部分包含所有从1到n的整数键,其他的所有键都储存在哈希部分中。

哈希部分其实就是一个哈希表,哈希表本质是一个数组,它利用哈希算法将键转化为数组下标,若下标有冲突即同一个下标对应了两个不同的键,则它会将冲突的下标上创建一个链表,将不同的键串在这个链表上,这种解决冲突的方法叫做:链地址法。

当我们把一个新键值赋给表时,若数组和哈希表已经满了,则会触发一个再哈希rehash。再哈希的代价是高昂的。

首先会在内存中分配一个新的长度的数组,然后将所有记录再全部哈希一遍,将原来的记录转移到新数组中。新哈希表的长度是最接近于所有元素数目的2的乘方。

local a = {}
//rehash 一次,更新分配大小为 2^0 即 1
a[1] = true

//rehash 一次,更新分配大小为 2^1 即 2
a[2] = true

//rehash 一次,更新分配大小为 2^2 即 4
a[3] = true
a[4] = true

//rehash 一次,更新分配大小为 2^3 即 8
a[5] = true
...

长度是从指数级增长的,也就是只有空表从头添加的时候 rehash 的次数较多

当需要创建非常多的小size的表时,应预先填充好表的大小。

3.字符串

与其他主流脚本语言不同的是,Lua在实现字符串类型有两方面不同。

  1. 所有的字符串在Lua中都只储存一份拷贝。 当新字符串出现时,Lua检查是否有其相同的拷贝,若没有则创建它,否则,指向这个拷贝。这可以使得字符串比较和表索引变得相当的快,因为比较字符串只需要检查引用是否一致即可; 但是这也降低了创建字符串时的效率,因为Lua需要去查找比较一遍。

    内部有字符串Map,相同字符串直接取出使用。

  2. 所有的字符串变量,只保存字符串引用,而不保存它的buffer。这使得字符串的赋值变得十分高效。 例如在Perl中,$x = $y,会将y的buffer整个的复制到x的buffer中,当字符串很长时,这个操作的代价将十分昂贵。而在Lua,同样的赋值,只复制引用,十分的高效。

    字符串只保存引用,所有相同字符串指向同一个内存地址。所以改变字符串也会导致所有使用的地方改变。

4.3R原则

3R原则(the rules of 3R)是:减量化(reducing),再利用(reusing)和再循环(recycling)三种原则的简称。

3R原则本是循环经济和环保的原则,但是其同样适用于Lua。

a.Reducing

有许多办法能够避免创建新对象和节约内存。

例如:如果你的程序中使用了太多的表,你可以考虑换一种数据结构来表示。

在这里插入图片描述

  1. 尽量不适用table中的hash部分,它比数组更耗存储;
  2. 如上例子所描述。正常的思维分类方式,并不适合大量数据的存储。
b.Reusing

如果无法避免创建新对象,我们需要考虑重用旧对象。

这个概念类似于,经常设置的全局对象,const,define的概念。对于在全局内,文件内,函数内,循环内,不变的对象,数值,可以在外部先创建出来,放到内部则每次调用会产生没必要,无意义的消耗。

c.Recycling

Lua自带垃圾回收器,所以我们一般不需要考虑垃圾回收的问题。

了解Lua的垃圾回收能使得我们编程的自由度更大。

Lua的垃圾回收器是一个增量运行的机制。即回收分成许多小步骤(增量的)来进行。

频繁的垃圾回收可能会降低程序的运行效率。

我们可以通过Lua的collectgarbage函数来控制垃圾回收器。

collectgarbage函数提供了多项功能:

  • 停止垃圾回收
  • 重启垃圾回收
  • 强制执行一次回收循环
  • 强制执行一步垃圾回收
  • 获取Lua占用的内存
  • 以及两个影响垃圾回收频率和步幅的参数。

对于批处理的Lua程序来说,停止垃圾回收collectgarbage(“stop”)会提高效率,因为批处理程序在结束时,内存将全部被释放。

什么叫批处理的Lua程序???

对于垃圾回收器的步幅来说,实际上很难一概而论。 更快幅度的垃圾回收会消耗更多CPU,但会释放更多内存,从而也降低了CPU的分页时间。只有小心的试验,我们才知道哪种方式更适合。

二.使用记录

1).insert、remove、concat、sort

Lua 自己实现排序sort比较方法,抛出错误invalid order function for sorting

以下都是是blog里的例子。

1. insert、remove

insert 和 remove 只能用于数组元素的插入和移出, 进行插入和移出时,会将后面的元素对齐起来。

主要是要在for循环里连续“删除/插入“时要注意。插入/删除 时,如果是使用for循环,应该从后往前遍历。或者使用while do,自己实现 index 的增减。

  1. 错误,第四个 3 没有被移除,ipairs 内部会维护一个变量记录遍历的位置,remove 掉第三个数字 3 之后,ipairs 下一个返回的值是 5 而不是 3 。

    local t = {1,2,3,3,5,3,6} 
    for i,v in ipairs(t) do 
        if v == 3 then 
            table.remove(t,i) 
        end 
    end 
    
  2. 错误,i=i-1 这段代码没有用,i 的值始终是从 1 到 #t,for 循环里修改 i 的值不起作用

    local t = {1,2,3,3,5,3,6} 
    for i=1, #t do 
        if t[i] == 3 then 
            table.remove(t,i) 
            i = i-1 
        end 
    end        
    
  3. 正确,从后往前遍历

      local t = {1,2,3,3,5,3,6} 
      for i=#t, 1, -1 do 
           if t[i] == 3 then 
               table.remove(t,i) 
           end 
       end  
    
  4. 正确,自己控制 i 的值是否增加

     local t = {1,2,3,3,5,3,6} 
    local i = 1 
    while t[i] do 
        if t[i] == 3 then 
            table.remove(t,i) 
        else 
            i = i+1 
        end 
    end
    

2.table.concat 可以将 table 的数组部分拼接成一个字符串,中间用 seq 分隔。

  lua 中字符串的存储方式与 C 不一样,lua 中的每个字符串都是单独的一个拷贝,拼接两个字符串会产生一个新的拷贝,如果拼接操作特别多,就会影响性能。
  当我们需要拼接”大数量“并且”首尾相接“的字符串时,可以先把他们insert进一个table,然后用concat来拼接。

3. sort 可以将 table 数组部分的元素进行排序,需要提供 comp 函数,comp(a, b) 如果 a 应该排到 b 前面,则 comp 要返回 true 。

注意,对于 a == b 的情况,一定要返回 false :

  1. 错误,a == b 时,返回的是true。 出现异常:attempt to compare number with nil

    local function comp(a,b) 
           return a <= b 
       end 
    table.sort(t,comp) 
    
  2. 错误。a == b 时,也是返回true。可能出现异常:invalid order function for sorting。也可能不报这个异常,但结果是错误的;

    local function comp(a,b) 
        if a == nil or b == nil then 
            return false 
        end 
        return a <= b 
    end 
    table.sort(t,comp) 
    

之所以 a == b 返回true 会引发这些问题,是因为 table.sort 在实现快速排序时没有做边界检测。

    for (;;) { 
      while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) {  // 未检测边界, i 会一直增加 
        if (i>=u) luaL_error(L, "invalid order function for sorting"); 
        lua_pop(L, 1); 
      } 
      while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) {  // 未检测边界, j 会一直减少 
        if (j<=l) luaL_error(L, "invalid order function for sorting"); 
        lua_pop(L, 1); 
      } 
      if (j<i) { 
        lua_pop(L, 3); 
        break; 
      } 
      set2(L, i, j); 
    } 

  看以上代码,如果 a == b 时返回 true 且边界上的几个值是相等的话, sort_comp 就无法阻止 i 继续增长,直到超出边界引发异常 attempt to compare number with nil。即使我们对 a 和 b 进行非空判断,也会因为 i 超过边界而引发异常 invalid order function for sorting。

2).Lua 垃圾回收

1.Lua 垃圾回收

Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。

垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久增大这个值会减少收集器的积极性

  1. 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。
  2. 设置这个值为 200 就会让收集器等到总内存使用量达到之前的两倍时才开始新的循环。

垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。

  1. 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。
  2. 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。

  如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。

2.垃圾回收器函数

Lua 提供了以下函数collectgarbage ([opt [, arg]])用来控制自动内存管理:

  • collectgarbage(“collect”): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:
  • collectgarbage(“count”): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。
  • collectgarbage(“restart”): 重启垃圾收集器的自动运行。
  • collectgarbage(“setpause”): 将 arg 设为收集器的间歇率。 返回 间歇率 的前一个值。
  • collectgarbage(“setstepmul”): 返回 步进倍率 的前一个值。
  • collectgarbage(“step”): 单步运行垃圾收集器。 步长"大小"由 arg 控制。如果收集器结束一个循环将返回 true 。
    • 传入 0 时,收集器步进(不可分割的)一步。
    • 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。
    • collectgarbage(“stop”): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。

Example:

  • collectgarbage(“setpause”, 200) : 内存增大 2 倍(200/100)时自动释放一次内存 (200 是默认值)。
  • **collectgarbage(“setstepmul”, 200) **:收集器单步收集的速度相对于内存分配速度的倍率,设置 200 的倍率等于 2 倍(200/100)。(200 是默认值)

3).随机数

1.随机数短时间内没变

Lua 随机数生成问题
Lua获取随机数

Q:短时间内,随机数基本不变化

A: os.time() 返回的时间是秒级的, 不够精确, 而 random() 还有个毛病就是如果 seed 很小或者seed 变化很小,产生的随机序列仍然很相似。

解决方案:

math.randomseed(tostring(os.time()):reverse():sub(1, 6))

把 time返回的数值字串倒过来(低位变高位), 再取高位6位。 这样, 即使 time变化很小, 但是因为低位变了高位, 种子数值变化却很大,就可以使伪随机序列生成的更好一些。

2.四舍五入

//math.random() * number,产生 1-number 的整数
math.floor(math.random() * number  + 0.5)

猜你喜欢

转载自blog.csdn.net/qq_28686039/article/details/109093463