Lua是否优化了“..”运算符?

时间:2013-10-02 14:10:13

标签: c string performance 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)

2 个答案:

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