本地声明(内置)Lua 函数以减少开销

Local declaration of (built-in) Lua functions to reduce overhead

人们常说应该在本地重新声明(某些)Lua 函数,因为这样可以减少开销。 但这背后的确切规则/原则是什么?我怎么知道哪些功能应该完成,哪些是多余的?还是应该为每个功能都完成,甚至是您自己的功能?

不幸的是,我无法从 Lua 手册中找出答案。

原理是每次你写table.insert时,Lua解释器会查找table中的"insert"条目,叫做table .实际上,这意味着 _ENV.table.insert - _ENV 是 Lua 5.2+ 中“全局变量”所在的位置。 Lua 5.1 有类似的东西,但不叫 _ENV。解释器在 _ENV 中查找字符串 "table",然后在 table 中查找字符串 "insert"。每次调用 table.insert 时两次 table 查找,然后才真正调用函数。

但是如果你把它放在局部变量中,那么解释器直接从局部变量中获取函数,这样会更快。它仍然需要查找它,以填充局部变量。

如果只在局部变量范围内调用一次函数是多余的,但这种情况很少见。对于已经声明为 local 的函数,没有理由这样做。它还使代码更难阅读,因此通常您不会这样做,除非它确实很重要(在运行很多次的代码中)。

我最喜欢在 Lua 中加快速度的工具是将 table 的所有可用内容放在名为 __index[=25 的 metatable 中=] 一个常见的例子是数据类型:string
它在他的 __index metatable 中包含所有字符串函数作为方法。
因此,您可以直接在字符串上做类似的事情...

print(('istaqsinaayok'):upper():reverse())
-- Output: KOYAANISQATSI

上面的逻辑...
在字符串中查找方法直接失败,因此将为该方法查找 __index 元方法。

我喜欢为数据类型编号实现相同的行为...

-- do debug.setmetatable() only once for all further defined/used numbers
math.pi = debug.setmetatable(math.pi, {__index = math})
-- From now numbers are objects ;-)
-- Lets output Pi but not using Pi this time
print((180):rad()) -- Pi calcing with method rad()
-- Output: 3.1415926535898

逻辑:如果不存在则查找 __index
仅落后一步:local
...恕我直言。

使用此方法的另一个示例...

-- koysenv.lua
_G = setmetatable(_G,
{ -- Metamethods
 __index = {}, -- Table constructor
 __name = 'Global Environment'
})

-- Reference whats in _G into __index
for key, value in pairs(_G) do
 getmetatable(_G)['__index'][key] = value
end

-- Remove all whats in __index now from _G
for key, value in pairs(getmetatable(_G)['__index']) do
 _G[key] = nil
end

return _G

当作为最后一个 require 启动时,它会将 _G 中的所有内容移动到新创建的元table 方法中 __index。
在那之后 _G 看起来完全是空的 ;-P
...但环境运行正常。

补充@user253751 已经说过的话:

代码质量

Lua 是一种非常灵活的语言。其他语言要求您导入您使用的标准库的部分; Lua 没有。 Lua通常提供一个不受污染的全局环境。如果您使用环境 _ENVsetfenv/getfenv on Lua 5.1 / LuaJIT),您仍希望能够访问 Lua图书馆。为此,您可以在更改环境之前对它们进行本地化;然后你可以为你的模块使用你的“干净”环境 / API table / class / 随便什么。这里的另一个选择是使用 metatables; metatable 链可能会很快变得毛茸茸,并且可能会损害性能,因为每次都需要失败的 table 查找来触发索引元方法。 local否则全局变量可以被视为导入它们的一种方式;举一个简单粗略的例子:

local print = print -- localize ("import") everything we need first
_ENV = {} -- set environment to clean table for module
function hello() -- this writes to _ENV instead of _G
    print("Hello World!")
end
hello() -- inside the environment, all variables set here are accessible
return _ENV -- "export" the API table

性能

非常小的挑剔:局部变量并不严格总是更快。在非常极端的情况下(即 很多上值 ),索引 table (如果是环境,则不需要上值,字符串 metatable 或之类的)实际上可能会更快。

我认为本地化变量对于优化编译器的许多优化是必需的,例如 LuaJIT 是适用的;否则 Lua 生成的代码很少。像 print 这样的全局变量也可能会在深层代码路径中的某个地方被覆盖——因此每次都必须重复索引操作;另一方面,对于本地人,口译员将对其范围有更多保证。因此,它能够检测仅写入一次的常量,例如在初始化时;对于全局变量,几乎不可能进行代码分析。