Lua学习

(地基工)Lua最牛的调试利器-------Decoda


Sublime Text 2 - 性感无比的代码编辑器!程序员必备神器!跨平台支持Win/Mac/Linux

Lua脚本语法说明(修订)

Cocos2dx 学习笔记(1) ---- Hello Lua!

Lua 编程技巧

内存泄漏:

lua内存泄漏查证

游戏逻辑层在Lua中的内存泄漏与防范

 

应该尽量使用 local 变量而非 global 变量。


这是 Lua 初学者最容易犯的错误。global 变量实际上是放在一张全局的 table 里的。global 变量实际上是利用一个 string (变量名作 key) 去访问这个 table 。虽然Lua5 的 table 效率很高 ,但是相对于 local 变量,依然有很大的效率损失。local 变量是直接通过 Lua 的堆栈访问的。有些 global 变量的访问是不经意的,比如我们有双重循环操作一个迭代的 table:

for k1,v1 in pairs(tbl) do
  for k2,v2 in pairs(v1) do
    ...
  end
end

这里,pairs 其实是一个全局变量应用的函数。如果我们这样做:

do
  local pairs=pairs
  for k1,v1 in pairs(tbl) do
    for k2,v2 in pairs(v1) do
      ...
    end
  end
end

效率会稍微提高一些。如果是单层循环,这样做就没有意义。因为 for ... in 循环中的 pairs 这个函数只会被调用一次,而不是每次循环都去调。我们的原则其实是,被多次读取的 global 变量,都应该提取出来放到 local 变量中。

应该尽量减少函数对 local 变量的可视范围


常用的技巧是使用 do ... end 。当定义一个 function 的时候,这个 function 向上所有可见的范围的 local 变量都会被作为 upvalue 和 function 本身绑定在一起,成为一个 closure 。太多的 local 变量虽然几乎没有速度损失,但是会带来额外的内存消耗。过多的内存消耗会过早引起 gc 。尤其是下面这样的例子:

function foo()
  local a,b,c
  ...
  return function() ... end
end
每次调用 foo() 时返回的 function 都会将它可见的 local 变量绑定在一起,形成新的 closure 。这些 local 变量包括 foo() 内的 a,b,c ,还包括 foo() 上面可见的一些 local 变量。这将有可能成为性能问题。btw, closure 和 function 是两个概念。这里是会产生一份 function ,但是 closure 再每次调用的时候都会产生一份新的。
有些时候,closure 的产生是可以避免的,这里有 一个例子 我从 http://lua-users.org 上摘抄下来:

function create()
  local e = {}
  e.x = 0
  e.update = function(self)  -- mark
    self.x = self.x + 1
  end
  return e
end

e1 = create()
e2 = create()
e3 = create()
这里,mark 这行,会被调用三次,每次均产生一个新的 closure 用于绑定 e (不过 function 本身只产生了一份) 。这里,local e 显然对 e.update 这个函数本身没有用,我们只需要把函数提到前面去即可。

do
  local function temp(self)
    self.x = self.x + 1
  end

  function create()
    local e = {
      x = 0,
      update = temp
    }
    return e
  end
end
这样,就可以避免 closure 的产生了。因为 function temp 不需要携带任何 upvalue 。btw, 我再这里修改了对 e 这个table 的赋值,这将稍微优于前面的赋值方法。因为编译器会得到 table 的大小,而避免多次分配内存。

如果这个例子扩展开,我们必须让 function temp 知道某些 create 调用时传入的信息,如:

function create(init)
  local e = {
    x = 0,
    reset = function temp(self) self.x = init end
  }
  return e
end

我们可以使用一个技巧,避免 function temp 绑定 upvalue init 。

do
  local reset

  function reset(self)
    self.x = self[reset]
  end

  function create(init)
    return {
      x = 0,
      [reset] = init,
      reset = reset
    }
  end
end

这里,把 init 这个值放到了 table 本身,reset 函数可以通过 self 取出来。key 选用的是 function reset ,这绝对可以避免和其它的 key 冲突。唯一的问题是让 table 大了一些。可是,lua table 是 hash 部分是按 2 的幂递增的,通常这不会带来额外的负担。一旦这个额外的 key 真的成了负担,即,增加这么一个 key 就会让 table 变成两倍大小。那么,我们可以用 metatable 解决这个问题。


do
  local funcs = {}
  local meta = {__index = funcs}

  function funcs.reset(self)
    self.x = self[funcs]
  end

  function create(init)
    local e={
      x = 0,
      [funcs] = init
    }
    setmetatable(e, meta)
    return e
  end
end

警惕临时变量 字符串的连接操作,会产生新的对象。


这是由 lua 本身的 string 管理机制导致的。lua 在 VM 内对相同的 string 永远只保留一份唯一 copy ,这样,所有字符串比较就可以简化为地址比较。这也是 lua 的 table 工作很快的原因之一。这种 string 管理的策略,跟 java 等一样,所以跟 java 一样,应该尽量避免在循环内不断的连接字符串,比如 a = a..x 这样。每次运行,都很可能会生成一份新的 copy 。

同样,记住,每次构造一份 table 都会多一份 table 的 copy 。比如在 lua 里,把平面坐标封装成 { x, y } 用于参数传递,就需要考虑这个问题。每次你想构造一个坐标对象传递给一个函数,{ 10,20 } 这样明确的写出,都会构造一个新的 table 出来。要么,我们想办法考虑 table 的重用;要么,干脆用 x,y 两个参数传递坐标。

同样需要注意的是以 function foo (...) 这种方式定义函数, ... 这种不定参数,每次调用的时候都会被定义出一个 table 存放不定数量的参数。

这些临时构造的对象往往要到 gc 的时候才被回收,过于频繁的 gc 有时候正是效率瓶颈。

使用 closure 代替 table 上面提到封装坐标的问题。诚然,我们可以用 { x=1,y=2 } 这样封装一个坐标。不过还有一个方法可供选择。它稍微轻量一点。


function point (x,y)
  return function () return x,y end
end

-- 使用范例
p=point(1,2)
print(p())
-- 输入 1 2


如果你愿意,还可以做的复杂一点:

function point (x,y)
  return function (idx)
    if idx=="x" then return x
    elseif idx=="y" then return y
    else return x,y end
  end
end

-- 使用范例
p=point(1,2)
print(p("x"))  -- 1
print(p("y"))  -- 2


x,y 实际被存放在 closure 里,每次调用 function point 都有一份独立的 closure。当然,function 的 code 只有一份。

你可能感兴趣的:(Lua学习)