在Lua中的本地与全局

每个来源都认为这一点是正确的:局部变量的访问比全局变量快。

在实际使用中,主要区别在于如何处理变量,因为它的范围是有限的,不可从代码的任何地方访问。

在理论上,局部变量不受非法更改的影响,因为它不可从错误的地方访问,并且查找该变量的性能更高。

现在我想知道这个概念的细节;一些代码部分如何访问,另外一些代码部分如何访问不到?性能的改进如何?

但最重要的问题是:假设我有一个变量 bazinga =“So cool.”,并且想要从任何地方更改它。由于字符串是公共的,我可以轻松这样做。但是,现在如果它被声明为局部的,并且我超出了范围,如果我通过X个函数传递变量,那么需要进行多少性能努力才能访问它?

func_3(bazinga)
    func_N(bazinga)
end
func_2(bazinga)
   func_3(bazinga)
end

func_1()
  local bazinga =“So cool。”
  func_2(bazinga)
end

到哪个点,局部变量仍然更具有性能优势,为什么?

我问你,由于维护在其中对象被传递多个函数的代码会变得混乱,我想知道它是否真的值得。

点赞
用户1442917
用户1442917

在进行任何比较之前,我要提醒您,您的担忧可能过早:先写代码,然后进行性能分析,然后再进行优化。在某些情况下在事后优化可能会很困难,但这不太可能是这种情况。

访问本地变量比访问全局变量快,因为访问全局变量会包括表查找(无论是在 _G 还是在 _ENV 中)。 LuaJIT 可能会优化其中的一些表访问,因此差异可能在那里不太明显。

在这种情况下,您无需牺牲访问的便捷性,因为您始终可以使用从函数到上值的访问来使本地变量可用:

local myvar
function getvar() return myvar end
function setvar(val) myvar = val end

-- elsewhere
setvar('foo')
print(getvar()) -- 输出 'foo'

使用 getvar 访问不会比作为全局变量访问 myvar 更快,但这使您可以使用 myvar 作为本地变量,并仍然可以从其他文件访问它(这可能是您希望它成为全局变量的原因)。

2014-10-02 05:52:21
用户3125367
用户3125367

在理论上,局部变量由于不能从错误的位置访问和查找变量的可靠性更高而免受非法更改的影响。

实际上,局部变量在实际中并不安全。这个概念是_lexical scoping_的一部分 - 这是一种名称解析方法,相较于动态和/或纯全局作用域而言,具有一些优势(如果你喜欢,也有缺点)。

性能的根源在于在Lua中,局部变量只是堆栈槽,按整数偏移索引,编译时(即在load()时)计算一次。但是全局变量实际上是全局表中的键,这是非常常规的表,因此任何访问都是非预先计算的查找。所有这些都取决于实现细节,可能因不同的语言甚至实现而异(如有人已经指出的,LuaJIT可以优化许多事情,因此因人而异)。

现在我想知道这个概念的细节;一些代码部分如何在技术上访问,而其他一些代码部分则无法访问?性能提高了多少?

从技术上讲,在5.1中,全局变量是具有特殊操作码的特殊表,而5.2删除了全局操作码并引入了每个函数的\_ENV upvalue。(我们所说的全局实际上是环境变量,因为查找进入函数的环境,该环境可能设置为除“全局表”之外的值,但让我们不要在飞行中更改术语)。所以,用5.2的术语来说,任何全局都只是全局表中的键值对,在每个函数中通过一个词法范围变量都可以访问。

现在谈到局部和词法作用域。正如您已经知道的,局部变量是堆栈槽。但是如果我们的函数使用外部范围的变量呢?在这种情况下,会创建一个特殊块来保存变量,它变成了_upvalue_。Upvalue是指向原始变量的无缝指针,当它的范围结束时,防止它被销毁(本地变量通常在逃逸范围时将不再存在,对吧?)。

但是主要的问题是:假设我有一个变量bazinga =“So cool.”,并且想要从某个地方进行更改。由于该字符串是公共的,所以我可以轻松地做到这一点。但是现在,如果它被声明为局部变量,而我不在范围内,则需要进行访问的性能努力是多少,如果我通过像这样的X函数交出变量:

在您的片段中,传递下调用堆栈的不是变量,而是值"So cool."(它是指向堆的指针,就像所有其他可垃圾回收值一样)。局部变量bazinga从未传递给任何函数,因为Lua没有称为var-parameters(Pascal)或指针/引用(C/C++) 的概念。每次调用函数时,所有参数都变成其局部变量,在我们的情况下,bazinga不是单个变量,而是具有相同值的不同堆栈帧中的一堆堆栈槽 - 指向堆的相同指针,其中包含"So cool."字符串。因此,在每个调用堆栈级别上都没有任何惩罚。

2014-10-02 06:57:44
用户7140551
用户7140551

你可以使用 os.clock() 自己测试局部变量和全局变量的性能。以下代码测试于一个运行在虚拟机上的 2.8 GHz 四核环境中。

-- 分配内存
local start = os.clock()
local timeend = os.clock()
local diff = timeend - start
local difflocal = {}
local diffglobal = {}

local x, y = 1, 1 -- 局部变量
a, b = 1, 1 -- 全局变量

-- 进行 10 次测试
for i = 0, 10, 1 do
    -- 开始测试局部变量
    start = os.clock()
    for ii = 0, 100000, 1 do
        y = y + ii
        x = x + ii
    end
    timeend = os.clock()
    -- 停止测试局部变量

    diff = (timeend - start) * 1000
    table.insert(difflocal, diff)

    -- 开始测试全局变量
    start = os.clock()
    for ii = 0, 100000, 1 do
        b = b + ii
        a = a + ii
    end
    timeend = os.clock()
    -- 停止测试全局变量

    diff = (timeend - start) * 1000
    table.insert(diffglobal, diff)
end

print(a)
print(b)
print(table.concat(difflocal, " ms, "))
print(table.concat(diffglobal, " ms, "))

输出:

55000550001
55000550001
2.033 ms, 1.979 ms, 1.97 ms, 1.952 ms, 1.914 ms, 2.522 ms, 1.944 ms, 2.121 ms, 2.099 ms, 1.923 ms, 2.12
9.649 ms, 9.402 ms, 9.572 ms, 9.286 ms, 8.767 ms, 10.254 ms, 9.351 ms, 9.316 ms, 9.936 ms, 9.587 ms, 9.58
2016-12-06 10:15:53