Lua是否优化了".."运算符?

Nic*_* M. 18 c string performance lua

我必须执行以下代码:

local filename = dir .. "/" .. base
Run Code Online (Sandbox Code Playgroud)

循环中数千次(这是一个打印目录树的递归).

现在,我想知道Lua是否一次性连接3个字符串(dir,"/",base)(即通过分配足够长的字符串来保存它们的总长度),或者它是否通过在内部执行它来实现这种效率低下的方式两个步骤:

local filename = (dir .. "/")              -- step1
                               .. base     -- step2
Run Code Online (Sandbox Code Playgroud)

最后一种方式是内存方式效率低下,因为分配了两个字符串而不是一个字符串.

我不太关心CPU周期:我主要关心内存消耗.

最后,让我概括一下这个问题:

Lua在执行以下代码时是否只分配一个字符串或4?

local result = str1 .. str2 .. str3 .. str4 .. str5
Run Code Online (Sandbox Code Playgroud)

顺便说一句,我知道我能做到:

local filename = string.format("%s/%s", dir, base)
Run Code Online (Sandbox Code Playgroud)

但我还没有对它进行基准测试(内存和CPU方面).

(顺便说一句,我知道table:concat().这会增加创建表的开销,所以我猜它在所有用例中都不会有用.)

奖金问题:

如果Lua没有优化".."运算符,那么定义用于连接字符串的C函数是否是一个好主意,例如utils.concat(dir, "/", base, ".", extension)

Lor*_*ica 35

尽管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 )
Run Code Online (Sandbox Code Playgroud)

..可以看到简单的优化分析以下脚本的反汇编字节码:

-- file "lua_06.lua"

local a = "hello"
local b = "cruel"
local c = "world"

local z = a .. " " .. b .. " " .. c

print(z)
Run Code Online (Sandbox Code Playgroud)

输出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"
Run Code Online (Sandbox Code Playgroud)

并假设s已经包含一个巨大的字符串(比方说,10MB).执行该语句会创建一个新字符串(10MB + 5个字符)并丢弃旧字符串.所以你刚刚为垃圾收集器创建了一个10MB的死对象来应对.如果你反复这样做,你最终会占用垃圾收集器.这是真正的问题,..这是典型的用例,需要收集表中的最终字符串的所有部分并table.concat在其上使用:这不会避免生成垃圾(所有部分将是调用后的垃圾table.concat),但你会大大减少不必要的垃圾.


结论

  • 使用..每当您连接数,可能短,字符串,或者你是不是在一个紧凑的循环.在这种情况下,table.concat可能会给你带来更糟糕的表现
    • 你必须创建一个表(通常你会扔掉);
    • 你必须调用该函数table.concat(函数调用开销会比使用内置..运算符多次影响性能).
  • table.concat如果需要连接多个字符串,请使用,尤其是在满足以下一个或多个条件时:
    • 您必须在后续步骤中执行此操作(..优化仅在同一表达式中起作用);
    • 你处于紧张的环境中;
    • 字符串很大(比如几个或更多).

请注意,这些只是经验法则.如果性能真的至关重要,那么您应该对代码进行分析.

无论如何Lua在处理字符串时与其他脚本语言相比要快得多,所以通常你不需要太在意.


Yu *_*Hao 10

在您的示例中,..操作员是否进行优化对于性能来说几乎不是问题,您不必担心内存或CPU.还有table.concat连接许多字符串.(参见Lua编程)供使用table.concat.

回到你的问题,在这段代码中

local result = str1 .. str2 .. str3 .. str4 .. str5
Run Code Online (Sandbox Code Playgroud)

Lua只分配一个新字符串,从Lua的相关源中检查这个循环luaV_concat:

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 */
Run Code Online (Sandbox Code Playgroud)

你可以看到Lua n在这个循环中连接字符串,但只是最后将一个字符串推回到堆栈,这是结果字符串.