本地声明(内置)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通常提供一个不受污染的全局环境。如果您使用环境 _ENV
(setfenv
/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
这样的全局变量也可能会在深层代码路径中的某个地方被覆盖——因此每次都必须重复索引操作;另一方面,对于本地人,口译员将对其范围有更多保证。因此,它能够检测仅写入一次的常量,例如在初始化时;对于全局变量,几乎不可能进行代码分析。
人们常说应该在本地重新声明(某些)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通常提供一个不受污染的全局环境。如果您使用环境 _ENV
(setfenv
/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
这样的全局变量也可能会在深层代码路径中的某个地方被覆盖——因此每次都必须重复索引操作;另一方面,对于本地人,口译员将对其范围有更多保证。因此,它能够检测仅写入一次的常量,例如在初始化时;对于全局变量,几乎不可能进行代码分析。