Lua的local变量探究

  欢迎参与讨论,转载请注明出处。

前言

  先前看到用好Lua+Unity,让性能飞起来—LuaJIT性能坑详解一文中提到:

3.2 寄存器分配失败->减少local变量、避免过深的调用层次
很不幸的一点是,arm中可用的寄存器比x86少。LuaJIT为了速度,会尽可能用寄存器存储local变量,但是如果local变量太多,寄存器不够用,目前JIT的做法是:放弃治疗(有兴趣可以看看源码中asm_head_side函数的注释)。因此,我们能做的,只有按照官方优化指引说的,避免过多的local变量,或者通过do end来限制local变量的生命周期。

  对此自然是可以理解的,哪怕是一般语言,local变量过多也会有堆栈溢出的问题。不过我对此一直有个隐忧:Lua是拥有模块级local变量的,不知是否也受此规则影响?尽管有此隐忧,却一直没有去做相关的探究。恰逢今日遇到相关话题,便来个刨根问底吧。

200限制

  首先的发现是:一段过程下最多拥有200个local变量,且do end不算。类似这样:

1
2
3
4
5
6
7
local Class = {}
local test1 = 1
local test2 = 2
... --to 199
return Class

  如果超过199,则会报出main function has more than 200 local variables的错误。当然这里说的是一段过程,所以函数是另算的,同样一个函数的过程最多也不能超过200个local变量(调用函数则算转入下一个过程了)。
  这个限制是Lua与LuaJIT共有的,显然是想限制local数量的泛滥。

函数嵌套调用

  接下来便是试试函数嵌套调用了:

1
2
3
4
5
6
7
8
9
10
function Class.Do(v)
if (v > n) then --n is a custom value
return
end
local test1 = 1
... -- to 199
Class.Do(v + 1)
end

  注意参数v也算是local变量的一员,所以test变量最多只能延伸到199个。以此进行递归调用的话,根据实验结果来看:

版本 嵌套上限 local变量上限
LuaJIT 325 65000
Lua5.3 4975 995000

  测试的环境为macOS x86-64,LuaJIT方面无论JIT开启与否结果皆一致。根据前文所言来看,到了ARM环境这个数量将会进一步下降。虽然从对比来看差距有点大,但实际上在函数调用方面也算够用了。

模块级local变量

  接下来便是我最关心的一点了:以上local变量上限是否会影响到模块级local变量?所谓模块级local变量即作用域为整个文件:

1
2
3
4
5
6
7
--test.lua
local Class = {}
local function Func()
end
...
return Class

  这种模块级local变量在Lua开发的应用还是很广泛的,它能有效的做到信息分隔的效果。但若是这些变量也受之前的上限规则影响,咁就扑街了!

  首先是测试加载多个满载local变量的模块:

1
2
3
for n=1, 5000 do
require("test" .. n)
end

  天可怜见,无论读取多少个文件,都不会存在上限问题。可见对于模块级local变量的处理是不一样的。到了这里基本上可以放心了,不过为防万一,我还做了模块的嵌套引用实验:

1
2
3
4
5
6
7
8
9
---test1.lua
local Class = {}
print("1")
local Next = require("test2")
local test1 = 1
local test2 = 2
... --to max
return Class

  以这种形式生成了5000个文件,以此进行嵌套引用,结果也是成功通过了。由此可见,对于模块级local变量是可以放心地去使用了。

后记

  尽管模块级local变量是可以随便用了,但是也要考虑到热更新方面的问题:若是选择使用模块级local变量去存储模块的数据,那么在热更新方面的处理将会变得十分麻烦。从这点考虑的话,模块级local变量最好只是用于引用别的模块为妙。