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字符串,但最后只将一个字符串推回到堆栈,这是结果字符串。