繁体   English   中英

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

[英]Does Lua optimize the ".." operator?

我必须执行以下代码:

local filename = dir .. "/" .. base

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

现在,我想知道 Lua 是否在一个 go 中连接 3 个字符串(dir、“/”、base)(即,通过分配一个足够长的字符串来保存它们的总长度),或者它是否通过在两个步骤:

local filename = (dir .. "/")              -- step1
                               .. base     -- step2

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

我不太关心 CPU 周期:我主要关心 memory 的消耗。

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

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

local result = str1 .. str2 .. str3 .. str4 .. str5

顺便说一句,我知道我可以这样做:

local filename = string.format("%s/%s", dir, base)

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

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

一个奖励问题:

如果 Lua 没有优化 ".." 运算符,那么定义一个 C function 用于连接字符串utils.concat(dir, "/", base, ".", extension) 、con、

虽然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  (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在处理字符串时与其他脚本语言相比要快得多,所以通常你不需要太在意。

在您的示例中, ..运算符是否进行优化对于性能来说几乎不是问题,您不必担心内存或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字符串,但是最后只将一个字符串推回到堆栈,这是结果字符串。

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

在这个特定的用例(和类似的用例)中,如果您担心创建大量垃圾表,则可以考虑重用表:

local path = {}
...
-- someplace else, in a loop or function:
path[1], path[2] = dir, base
local filename = table.concat(path, "/")
path[1], path[2] = nil
...

您甚至可以将其概括为“concat”实用程序:

local rope = {}
function string_concat(...)
    rope = {...} -- prepare rope
    local res = table.concat(rope)
    for i = 1, select("#", ...) do rope[i] = nil end -- clear rope
    return res
end

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM