lua:全局变量 vs table 入口变量

lua: global var vs table entry var

在 lua 中,当你在 table 中有一个函数时,在函数中声明一个全局变量与在 table 中将变量声明为一个条目有什么区别(如果有的话)?下例中的变量是 x。

dog={x=33,
func=function(self)
self.x=self.x*self.x
end
}

cat={func=function()
x=33
x=x*x
end
}

在 dog 中,我可以使用 self 的属性通过 dog:func() 而不是 dog.func(dog) 来调用函数。但除此之外,在两者之间进行选择时是否有任何性能方面的考虑因素?这些示例在循环中调用时有点不同,但在循环之外?

这两个代码片段做的事情截然不同。 dog.funcself.x 设置为其先前值的平方。 cat.func 将全局 x 设置为 1089。您无法真正比​​较两个功能如此不同的事物之间的性能。

嗯,我听说关于优化的首要规则是 “不要做!”“还不要做!” .

有一份官方文档公开了一些optimize Lua code的方法,我推荐它。最重要的规则是局部变量优于全局变量,因为全局变量比局部变量慢 30%。

我们可以对前面的代码做的第一件事是编译它并检查字节码指令以了解在执行时发生了什么。我将第一个函数存储在“test-1.lua”中,将第二个函数存储在“test-2.lua”中。

> cat test-1.lua
dog={x=33,
func=function(self)
self.x=self.x*self.x
end
}

function TEST ()
  dog:func()
end
> luac54 -l -s test-1.lua
#
#(part of output omitted for clarity)
#
# Function: dog.func
#
function <test-1.lua:2,4> (6 instructions at 0000000000768740)
1 param, 3 slots, 0 upvalues, 1 local, 1 constant, 0 functions
        1       [3]     GETFIELD        1 0 0   ; "x"
        2       [3]     GETFIELD        2 0 0   ; "x"
        3       [3]     MUL             1 1 2
        4       [3]     MMBIN           1 2 8   ; __mul
        5       [3]     SETFIELD        0 0 1   ; "x"
        6       [4]     RETURN0
#
# Function: TEST (function to call dog.func)
#
function <test-1.lua:7,9> (4 instructions at 00000000000a8a90)
0 params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
        1       [8]     GETTABUP        0 0 0   ; _ENV "dog"
        2       [8]     SELF            0 0 1k  ; "func"
        3       [8]     CALL            0 2 1   ; 1 in 0 out
        4       [9]     RETURN0

所以,如果我们要执行10次TEST,我们至少需要执行10*(4+6)条字节码指令,也就是100条字节码指令。

> cat test-2.lua
cat={func=function()
x=x*x
end
}

x=33

function TEST ()
  cat.func()
end
> luac54 -l -s test-2.lua
#
#(part of output omitted for clarity)
#
# Function: cat.func
#
function <test-2.lua:1,3> (6 instructions at 00000000001b87f0)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
        1       [2]     GETTABUP        0 0 0   ; _ENV "x"
        2       [2]     GETTABUP        1 0 0   ; _ENV "x"
        3       [2]     MUL             0 0 1
        4       [2]     MMBIN           0 1 8   ; __mul
        5       [2]     SETTABUP        0 0 0   ; _ENV "x"
        6       [3]     RETURN0
#
# Function: TEST (function to call cat.func)
#
function <test-2.lua:8,10> (4 instructions at 00000000001b8a80)
0 params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
        1       [9]     GETTABUP        0 0 0   ; _ENV "cat"
        2       [9]     GETFIELD        0 0 1   ; "func"
        3       [9]     CALL            0 1 1   ; 0 in 0 out
        4       [10]    RETURN0

所以,如果我们要执行TEST 10次,我们至少需要执行10*(4+6)条字节码指令,也就是100条字节码指令....这正是和第一个版本一样!

显然,所有字节码指令的执行时间并不相同。一些指令在 C 运行时会比其他指令花费更多的时间。添加两个整数可能比分配一个新的 table 并初始化一些字段要快得多。到那时,我们可以尝试做一个肮脏且毫无意义的事情 microbenchmark 来给我们一个想法。

可以将此代码复制并粘贴到 Lua 解释器中:

> cat dirty-and-pointess-benchmark.lua
dog={x=33,
func=function(self)
self.x=self.x*self.x
end
}

cat={func=function()
x=x*x
end
}

x=33

function StartMeasure ()
  StartTime = os.clock()
end

function StopMeasure (TestName)
  local Duration = os.clock() - StartTime
  print(string.format("%s: %f sec", TestName, Duration))
end

function DoTest1 (Count)
  for Index = 1, Count do
    dog:func()
  end
end

function DoTest2 (Count)
  for Index = 1, Count do
    cat.func()
  end
end

COUNT = 5000000000

StartMeasure()
DoTest1(COUNT)
StopMeasure("VERSION_1")

StartMeasure()
DoTest2(COUNT)
StopMeasure("VERSION_2")

这段代码在我的电脑上给出了这个结果:

VERSION_1: 246.816000 sec
VERSION_2: 250.412000 sec

显然,对于大多数程序而言,差异可能可以忽略不计。我们应该总是尽量多花时间写正确的程序,少花时间做微基准测试。

首先你应该改变

cat={func=function()
x=33
x=x*x
end
}

x=33
cat={func=function()
x=x*x
end
}

现在我们有相同的操作。

如果我 运行 两个函数都运行 10000 次,我最终会 cat.func()dog:func()

慢几个百分点

这并不奇怪,因为索引局部变量比索引全局变量更快。

要加速 cat 你可以这样做:

x=33
cat={func=function()
local _x = x
x = _x*_x
end
}

最快的解决方案可能是

dog={x=33,
func=function(self)
  local x = self.x
  self.x = x*x
end
}

您甚至可以通过将表和 x 设为本地来提高速度。

通常你做那样的事情不会赢得任何重要的东西。 过早的优化是一个很大的禁忌,你应该问问自己你真正想要解决的问题是什么。

如果您甚至还不够了解 Lua 来为您的代码编写一个简单的基准测试,那么从您的代码中挤出最后的百分比也是没有意义的……只是一个想法。