Lua是否优化了“..”运算符?
我必须执行以下代码:
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)
?
尽管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(12条指令003E40A0) 0 + params,8个槽,1个upvalue,4个locals,5个常量,0个函数 1 [3] LOADK 0 -1; “你好” 2 [4] LOADK 1-2; “残忍” 3 [5] LOADK 2 -3; “世界” 4 [7]移动3 0 5 [7] LOADK 4 -4; “” 6 [7]移动5 1 7 [7] LOADK 6 -4; “” 8 [7]移动7 2 9 [7] CONCAT 3 3 7 10 [9] GETTABUP 4 0 -5; _ENV“打印” 11 [9]移动5 3 12 [9] CALL 4 2 1 13 [9]返回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
字符串,但最后只将一个字符串推回到堆栈,这是结果字符串。