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

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

之前,我们从C#代码的角度,为大家介绍了CPU耗时和堆内存的知识点,详见文末的知识点汇总。本期,我们将开启对本地资源检测中LuaCheck功能的解读,并结合简单的代码实例来讲解Lua检测中的具体情况,力图以浅显易懂的表达,让职场萌新或优化萌新能够深入理解。

Lua中的全局变量实现

全局变量在大多数语言中都属于“双刃剑”:全局变量可以表达程序中的全局概念,但是全局变量的使用会带来很多隐患。对于Lua这样的嵌入式语言,它是由宿主应用通过调用代码段(Chunk)来实现功能的。但“程序”概念并不明确,所以Lua语言为了解决这个问题,选择不使用全局变量,转而对全局变量进行模拟,把所有的全局变量保存在一个称为全局环境(global environment)的普通表中。

Lua 5.2版本中修改了environment的概念,新增使用一个名为_ENV、以语法糖形式存在的预定义上值来模拟全局变量。例如一个代码段:

local z=10
x=y+z

因为Lua语言把所有的代码段都当作匿名函数进行处理,实际上编译器会将代码段编译成如下形式:

local _ENV = smoe value
return function (...)
    local z = 10
    _ENV.x = _ENV.y + z
end

当_ENV的值是全局环境(global environment)表的时候,Lua便模拟出“x、y是全局变量”的情况。但实际上Lua语言是没有全局变量概念的,关于_ENV知识的详细解读可以阅读云风大神的Blog


Lua中全局变量的危害

1)在程序开发过程中,Lua语言中的全局变量不需要声明就可以使用,因此随着功能模块的增多,全局变量就会变得非常不稳定,稍有不慎便会重定义覆盖全局变量,产生各种不易查找的灾难性Bug。

2)对于不是局部变量的访问,Lua会重定向到全局环境表_G。而局部变量始终具有优先访问权,因此如果一个全局变量和一个局部变量同名且同时存在,那么访问该变量时,得到的始终是局部变量的值。

3)如果没有人为设置,Lua中通过模拟得到的全局变量,均会被_G表引用。但如果该全局变量引用了Unity中的Object对象,就会导致Unity中的Object对象被Destroy之后,_G表中的全局变量依然引用着该Object对象,引发泄露问题。更多关于Lua造成的堆内存泄露问题可以阅读本篇文章。

4)开发过程中多个虚拟机同时操作一个变量时,会导致线程不安全。


报告检测规则解读

考虑到以上全局变量的使用隐患,因此我们在使用Lua语言开发时,应当尽量少用全局变量。

UWA本地资源检测服务中,是否允许全局变量的下拉框中有四个选项:

Not:表示不允许使用全局变量,任何地方出现的全局变量都将被视为不通过。

Allow-Defined:表示允许在Main-Chunk中声明全局变量后,在函数体等地方使用。

Main-Chunk指的是代码主流程块,例如下边的代码,foo变量位于Main-Chunk中,而qu变量在函数体中,不位于Main-Chunk中。

foo = 4
print(foo)

function f()
   qu = 4
   print(qu)
end

Allow-Defined-Top:表示允许在任何地方声明全局变量。

Add-Custom:选择该选项后,会弹出两个输入框(如下图),表示允许维护白名单,“自定义全局变量”表示白名单中的全局变量允许被声明、使用和修改;“自定义只读全局变量或字段”表示白名单中的全局变量及其字段允许被声明和使用,不允许被修改。这里的只读全局变量来源于用户自己的设定,可以维护一些不会被更改的数据表。


1、设置一个未定义的全局变量

本条规则下,检测的代码结果会和我们在UWA Scan Setting内的设置紧密相连。例如,在Main-Chunk中写如下代码:

function f()
   baz = 5
end

选择Not模式,上述声明的全局变量(f、baz)会被视为不通过;

选择Allow-Defined-Top模式,在非Main-Chunk的代码段中声明的全局变量(baz)会被视为不通过;

选择Add-Custom模式,声明的全局变量(f、baz)如果不在白名单中,则会被视为不通过。


2、更改一个未定义的全局变量

例如,在项目中写如下代码:

server.foo = "bar"

在没有声明全局变量server的情况下,我们对全局变量server进行了更改,赋值了一个键值对。

改正方式:在赋值之前进行声明:

server={}
server.foo = "bar"

3、访问一个未定义的全局变量

同样,如果在没有声明全局变量server的情况下,我们写了如下代码:

server.sessions["hey"] = "you"

那实际上是在没有进行声明的前提下,访问了全局变量server的“sessions”域。
改正方式:在访问之前进行声明。


4、试图设置一个只读的全局变量

如果选择Add-Custom模式,那么在“自定义只读全局变量或字段”白名单中存在的全局变量理应保持为“只读”的属性。

例如:设定全局变量string是只读全局变量,但出现如下代码:

string = "foo"

这就意味着应当保持“只读”属性的string,它的内容却被更改了。本条规则检测的就是这些发生变动的“只读”全局变量,开发团队需要去检查那些发生修改操作的代码,判断具体的使用情况是否符合初衷。


5、试图设置一个全局变量的只读域

function fun()
    bar.ff={}
end

这就导致只读全局变量bar的域ff被更改,在使用上和“只读”发生了冲突,需要进一步的排查。


6、未被使用的隐式定义的全局变量

此处指的是那些被定义了、但从未被使用过的全局变量。例如:声明了全局变量fun,但在之后的代码中从未使用过它:

function fun()
   baz = 5
end

开发团队在经过必要的确认后即可删除这些多余的全局变量。


7、试图设置一个全局变量的未定义域

一种可能的情况如下:

function table.clone()
    print('huh')
end

在没有声明table这个全局变量的情况下,我们给全局变量table赋值了一个表并添加了一个函数作为value。在这种情况下,开发团队就要检查相关全局变量和对应域的具体使用。


8、试图访问一个全局变量的未定义域

类似的:

function table.clone()
    print('huh')
end

table.clone()

类比上一条,在没有声明table这个全局变量的情况下,访问了table.clone()。在经过进一步的检查后,开发团队再决定是否进行声明的补充或者删除访问。


希望以上这些知识点能在实际的开发过程中为大家带来帮助。需要说明的是,每一项检测规则的阈值都可以由开发团队依据自身项目的实际需求设置合适的阈值范围,这也是本地资源检测的一大特点。同时,也欢迎大家来使用UWA推出的本地资源检测服务,可帮助大家尽早对项目建立科学的检测规范。

万行代码屹立不倒,全靠基础掌握得好!

相关推荐
《C#代码优化:斩断伸向堆内存的“黑手”》
《C#代码优化:拯救你的CPU耗时》
《场景检测:面片、光影和物理属性》
《场景检测:Audio Listener、RigidBody和Prefab连接》
《场景检测:雾效、Canvas和碰撞体》
《特效优化2:效果与性能的博弈》
《特效优化:发现绚丽背后的质朴》

性能黑榜相关阅读

《那些年给性能埋过的坑,你跳了吗?》
《那些年给性能埋过的坑,你跳了吗?(第二弹)》
《掌握了这些规则,你已经战胜了80%的对手!》