Lua 性能,内存优化

1.字符串

  • 原理

    1. Lua 的字符串都是内化的(internalized);这意味着字符串在 Lua 中都只有一份拷贝。每当一个新字符串出现时,Lua 会先检查这个字符串是否已经有一份拷贝,如果有,就重用这份拷贝。内化(internalization)使字符串比较及表索引这样的操作变得非常快,但是字符串的创建会变慢。
    2. Lua 的字符串变量从来不会包含字符串本身,包含的只是字符串的引用。这种实现加快了某些字符串操作。
  简单的说lua维护了一个table存放了所有的字符串。
  任何新创建的字符串都会先hash去table查找一下,有的话直接返回字符串的引用。
  没有的话创建个新的字符串放入table,返回新的字符串的引用。

  --引用列子
  local value = "a" 
  value  = value .."b"
  print(value )  -- 输出 'ab'
  现在 lua string这个大table里,就有了  'a'  和 'ab' 两个字符串了。value 实际引用的是 'ab'。

  --字符串的连接列子
  'x' .. 'y' .. 'z'
  这种就是 x找一回,xy找一回,xyz找一回。
  生成3个串 找3回最后table里有了'x','xy','xyz' 三个字符串了。
  • 优化

    1. 使用运算符 ' .. '
      每次拼接都需要申请新的空间,旧的result对应的空间会在某时刻被Lua的垃圾回收期GC,且随着result不断增长,越往后会开辟更多新的空间,并进行拷贝操作,产生更多需要被GC的空间,所以性能降低。

    2. 使用table.concat (table [, sep [, start [, end]]])函数
      table.concat 底层拼接字符串的方式也是使用运算符.. ,但是其使用算法减少了使用运算符..的次数,减少了GC,从而提高效率。

2.Table

  • 原理

    1. Lua 实现表的算法颇为巧妙。每个表包含两部分:数组(array)部分和哈希(hash)部
      分,数组部分保存的项(entry)以整数为键(key),从 1 到某个特定的 n,(稍后会讨
      论 n 是怎么计算的。)所有其他的项(包括整数键超出范围的)则保存在哈希部分。
      顾名思义,哈希部分使用哈希算法来保存和查找键值。它使用的是开放寻址(open
      address)的表,意味着所有的项都直接存在哈希数组里。键值的主索引由哈希函数给出;
      如果发生冲突(两个键值哈希到相同的位置),这些键值就串成一个链表,链表的每个元素
      占用数组的一项。
    2. 执行扩容的过程叫做rehash,每次rehash时,会遍历整个table的数组部分和哈希表部分,统计其中有效的键值对,大小不够,则会扩容,扩容后的大小为2的整数幂次方,且保证rehash操作后整个数组部分的使用率大于50%。每次rehash都很耗时,使用table,我们应该尽量减少rehash。
  • 优化

    1. 初始化优化减少, rehash的次数.
    local param = {};param.type = 1;param.id = 1; param.name = "lua"; 
    --优化成
    local param = {type= 1, id = 1,name = "lua"};
    

    2. 扩容

    local a = {}
    --a = 0
    for i = 1, 3 do
     a[i] = true
     --i == 1 a == 1
     --i == 2 a == 2
     --i == 3 a == 4
    end
    --优化为
    local a = {0,0,0}
    for i = 1, 3 do
     a[i] = true
    end
    
    当新key要加入,table大小从0->1,1->2,2->4,类似这种预先分配好空间能减少内存分配。

3.局部变量

  • 原理

    Lua 使用了一个基于寄存器的虚拟机。这些「寄存器」
    跟 CPU 中真实的寄存器并无关联,因为这种关联既无可移植性,也受限于可用的寄存器数
    量。Lua 使用一个栈(由一个数组加上一些索引实现)来存放它的寄存器。每个活动的
    (active)函数都有一份活动记录(activation record),活动记录占用栈的一小块,存放
    着这个函数对应的寄存器。因此,每个函数都有其自己的寄存器。由于每条指令只有 8 个
    bit 用来指定寄存器,每个函数便可以使用多至 250 个寄存器。
    Lua 的寄存器如此之多,预编译时便能将所有的局部变量存到寄存器中。所以,在 Lua 中
    访问局部变量是很快的。
  • 优化

    1. 高频调用类优化
    local x = math.sin(i) 
    --优化成
    local sin = math.sin
    local x = sin(i)
    
    1. 在大项目基于元表,元方法实现的类和继承时,local优化是很高效的。
      local变量优化

4.元表与元方法

  • 原理

因为Lua本身是没有面向对象支持的,但在项目开发中需要面向对象编程,于是很多人用Lua本身的数据结构table+元表来模拟面向对象实现类、继承、多重继承。当元方法 __index 和 __newindex 变成函数时,会因为 函数本身的复杂度导致逻辑消耗时间变多。在大型项目里是不小的性能开销。

  1. 元方法 __index 用来对表访问,访问规则:
    1.在表中查找,如果找到,返回该元素,找不到则继续。
    2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
    3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

  2. 元方法 __newindex 用来对表更新,更新规则:
    1.如果是表已存在的索引键,则直接更新,没有的话就继续。
    2.解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
    3.如果不存在则直接对表赋值。

  • 优化

    1. 利用local话,提升访问速度。
    2. C++、C 去实现,毕竟C、C++的效率高。

5.函数

  1. 参数优先boolean, number, string, function,少table。防止new table 频繁rehash,gc。
  2. Lua编译一个函数时,会为它生成一个原型(prototype),其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua执行一个形如function...end 这样的表达式时,它就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(environment,用来查找全局变量的表)的引用以及一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。
function f1(n)
  local function f2()
    --用来查找全局变量的表)的引用以及一个由所有upvalue引用组成的数组
    -{n}
    print(n)
  end
  n=n + 10
  return  f2
end
g1  = f1(1979)
g1()--打印出1989

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。