[英]Does Lua optimize the ".." operator?
我必须执行以下代码:
local filename = dir .. "/" .. base
循环中数千次(这是打印目录树的递归)。
现在,我想知道 Lua 是否在一个 go 中连接 3 个字符串(dir、“/”、base)(即,通过分配一个足够长的字符串来保存它们的总长度),或者它是否通过在两个步骤:
local filename = (dir .. "/") -- step1
.. base -- step2
最后一种方式在内存方面效率低下,因为分配了两个字符串而不是一个。
我不太关心 CPU 周期:我主要关心 memory 的消耗。
最后,让我概括一下这个问题:
Lua 在执行以下代码时是否只分配一个字符串,还是 4 个?
local result = str1 .. str2 .. str3 .. str4 .. str5
顺便说一句,我知道我可以这样做:
local filename = string.format("%s/%s", dir, base)
但我还没有对其进行基准测试(内存和 CPU 方面)。
(顺便说一句,我知道 table:concat()。这增加了创建表的开销,所以我想它不会在所有用例中都有益。)
一个奖励问题:
如果 Lua 没有优化 ".." 运算符,那么定义一个 C function 用于连接字符串utils.concat(dir, "/", base, ".", extension)
、con、
虽然Lua对..
使用进行了简单的优化,但你仍然应该小心地在紧密循环中使用它,特别是在连接非常大的字符串时,因为这会产生大量垃圾,从而影响性能。
连接多个字符串的最佳方法是使用table.concat
。
table.concat
允许您将表用作所有要连接的字符串的临时缓冲区,并且只有在完成向缓冲区添加字符串后才执行连接,如下面的愚蠢示例所示:
local buf = {}
for i = 1, 10000 do
buf[#buf+1] = get_a_string_from_somewhere()
end
local final_string = table.concat( buf )
可以看到..
的简单优化分析以下脚本的反汇编字节码:
-- file "lua_06.lua"
local a = "hello"
local b = "cruel"
local c = "world"
local z = a .. " " .. b .. " " .. c
print(z)
luac -l -p lua_06.lua
的输出如下(对于Lua 5.2.2):
main (13 instructions at 003E40A0) 0+ params, 8 slots, 1 upvalue, 4 locals, 5 constants, 0 functions 1 [3] LOADK 0 -1 ; "hello" 2 [4] LOADK 1 -2 ; "cruel" 3 [5] LOADK 2 -3 ; "world" 4 [7] MOVE 3 0 5 [7] LOADK 4 -4 ; " " 6 [7] MOVE 5 1 7 [7] LOADK 6 -4 ; " " 8 [7] MOVE 7 2 9 [7] CONCAT 3 3 7 10 [9] GETTABUP 4 0 -5 ; _ENV "print" 11 [9] MOVE 5 3 12 [9] CALL 4 2 1 13 [9] RETURN 0 1
您可以看到只生成了一个CONCAT
操作码,尽管脚本中使用了许多..
运算符。
要完全了解何时使用table.concat
您必须知道Lua字符串是不可变的 。 这意味着每当您尝试连接两个字符串时,您确实创建了一个新字符串(除非结果字符串已被解释器实例化,但这通常不太可能)。 例如,请考虑以下片段:
local s = s .. "hello"
并假设s
已经包含一个巨大的字符串(比方说,10MB)。 执行该语句会创建一个新字符串(10MB + 5个字符)并丢弃旧字符串。 所以你刚刚为垃圾收集器创建了一个10MB的死对象来应对。 如果你反复这样做,你最终会占用垃圾收集器。 这是真正的问题..
这是典型的使用情况下,有必要收集,最后一个字符串的所有片段在表中,并使用table.concat
就可以了:这将无法避免垃圾的产生(调用table.concat
后所有的部分都将是垃圾,但是你会大大减少不必要的垃圾。
..
在这种情况下, table.concat
会给你带来更差的性能,因为:
table.concat
(函数调用开销比使用内置的..
运算符多次影响性能)。 table.concat
,尤其是在满足以下一个或多个条件时:
..
优化只在同一个表达式中起作用); 请注意,这些只是经验法则。 如果性能真的至关重要,那么您应该对代码进行分析。
无论如何Lua在处理字符串时与其他脚本语言相比要快得多,所以通常你不需要太在意。
在您的示例中, ..
运算符是否进行优化对于性能来说几乎不是问题,您不必担心内存或CPU。 还有用于连接多个字符串的table.concat
。 (请参阅Lua中的编程 )以了解table.concat
。
回到你的问题,在这段代码中
local result = str1 .. str2 .. str3 .. str4 .. str5
Lua只分配一个新字符串,从luaV_concat
Lua相关源检查这个循环:
do { /* concat all strings */
size_t l = tsvalue(top-i)->len;
memcpy(buffer+tl, svalue(top-i), l * sizeof(char));
tl += l;
} while (--i > 0);
setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl));
total -= n-1; /* got 'n' strings to create 1 new */
L->top -= n-1; /* popped 'n' strings and pushed one */
你可以看到Lua在这个循环中连接了n
字符串,但是最后只将一个字符串推回到堆栈,这是结果字符串。
顺便说一句,我知道 table:concat()。 这增加了创建表的开销,所以我想它不会在所有用例中都有益。
在这个特定的用例(和类似的用例)中,如果您担心创建大量垃圾表,则可以考虑重用表:
local path = {}
...
-- someplace else, in a loop or function:
path[1], path[2] = dir, base
local filename = table.concat(path, "/")
path[1], path[2] = nil
...
您甚至可以将其概括为“concat”实用程序:
local rope = {}
function string_concat(...)
rope = {...} -- prepare rope
local res = table.concat(rope)
for i = 1, select("#", ...) do rope[i] = nil end -- clear rope
return res
end
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.