我必须执行以下代码:
local filename = dir .. "/" .. base
循环中数千次(这是一个打印目录树的递归)。
现在,我想知道Lua是否一次性连接3个字符串(dir,“/”,base)(即通过分配足够长的字符串以保持其总长度)或者是否通过执行这样做效率低的方式它内部分两步:
local filename = (dir .. "/") -- step1
.. base -- step2
这种最后一种方式在内存方面效率低下,因为分配了两个字符串而不是一个字符串。
我不太关心CPU周期:我主要关心内存消耗。
最后,让我概括一下这个问题:
Lua在执行以下代码时是否只分配一个字符串或4?
local result = str1 .. str2 .. str3 .. str4 .. str5
顺便说一句,我知道我能做到:
local filename = string.format("%s/%s", dir, base)
但我还没有对它进行基准测试(内存和CPU明智)。
(顺便说一下,我知道table:concat()。这会增加创建表的开销,所以我猜它在所有用例中都不会有用。)
奖金问题:
如果Lua没有优化“..”运算符,那么定义用于连接字符串的C函数是个好主意,例如: utils.concat(dir, "/", base, ".", extension)
?
答案 0 :(得分:33)
虽然Lua对..
用法进行了简单的优化,但你仍然应该小心地在紧密循环中使用它,特别是在连接非常大的字符串时,因为这会产生大量垃圾,从而影响性能。 / p>
连接多个字符串的最佳方法是使用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相当快,所以通常你不需要太在意。
答案 1 :(得分:10)
在您的示例中,..
运算符是否进行优化对于性能来说几乎不是问题,您不必担心内存或CPU。并且table.concat
用于连接多个字符串。 (请参阅Programming in 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
个字符串,但只是最后将一个字符串推回到堆栈,这是结果字符串。