简体   繁体   English

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

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

I have to execute the following code:我必须执行以下代码:

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

thousands of times in a loop (it's a recursion that prints a directory tree).循环中数千次(这是打印目录树的递归)。

Now, I wonder whether Lua concatenates the 3 strings (dir, "/", base) in one go (ie, by allocating a string long enough to hold their total lengths) or whether it does this the inefficient way by doing it internally in two steps:现在,我想知道 Lua 是否在一个 go 中连接 3 个字符串(dir、“/”、base)(即,通过分配一个足够长的字符串来保存它们的总长度),或者它是否通过在两个步骤:

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

This last way would be inefficient memory-wise because two strings are allocated instead of just one.最后一种方式在内存方面效率低下,因为分配了两个字符串而不是一个。

I don't care much about CPU cycles: I care mainly about memory consumption.我不太关心 CPU 周期:我主要关心 memory 的消耗。

Finally, let me generalize the question:最后,让我概括一下这个问题:

Does Lua allocate only one string, or 4, when it executes the following code? Lua 在执行以下代码时是否只分配一个字符串,还是 4 个?

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

BTW, I know that I could do:顺便说一句,我知道我可以这样做:

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

But I've yet to benchmark it (memory & CPU wise).但我还没有对其进行基准测试(内存和 CPU 方面)。

(BTW, I know about table:concat(). This has the added overhead of creating a table so I guess it won't be beneficial in all use cases.) (顺便说一句,我知道 table:concat()。这增加了创建表的开销,所以我想它不会在所有用例中都有益。)

A bonus question:一个奖励问题:

In case Lua doesn't optimize the ".." operator, would it be a good idea to define a C function for concatenating strings, eg utils.concat(dir, "/", base, ".", extension) ?如果 Lua 没有优化 ".." 运算符,那么定义一个 C function 用于连接字符串utils.concat(dir, "/", base, ".", extension) 、con、

Although Lua performs a simple optimization on .. usage, you should still be careful to use it in a tight loop, especially when joining very large strings, because this will create lots of garbage and thus impact performance. 虽然Lua对..使用进行了简单的优化,但你仍然应该小心地在紧密循环中使用它,特别是在连接非常大的字符串时,因为这会产生大量垃圾,从而影响性能。

The best way to concatenate many strings is with table.concat . 连接多个字符串的最佳方法是使用table.concat

table.concat lets you use a table as a temporary buffer for all the strings to be concatenated and perform the concatenation only when you are done adding strings to the buffer, like in the following silly example: table.concat允许您将表用作所有要连接的字符串的临时缓冲区,并且只有在完成向缓冲区添加字符串后才执行连接,如下面的愚蠢示例所示:

local buf = {}
for i = 1, 10000 do
    buf[#buf+1] = get_a_string_from_somewhere()
end
local final_string = table.concat( buf )

The simple optimization for .. can be seen analyzing the disassembled bytecode of the following script: 可以看到..的简单优化分析以下脚本的反汇编字节码:

-- file "lua_06.lua"

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

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

print(z)

the output of luac -l -p lua_06.lua is the following (for Lua 5.2.2): 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

You can see that only a single CONCAT opcode is generated, although many .. operators are used in the script. 您可以看到只生成了一个CONCAT操作码,尽管脚本中使用了许多..运算符。


To fully understand when to use table.concat you must know that Lua strings are immutable . 要完全了解何时使用table.concat您必须知道Lua字符串是不可变的 This means that whenever you try to concatenate two strings you are indeed creating a new string (unless the resulting string is already interned by the interpreter, but this is usually unlikely). 这意味着每当您尝试连接两个字符串时,您确实创建了一个新字符串(除非结果字符串已被解释器实例化,但这通常不太可能)。 For example, consider the following fragment: 例如,请考虑以下片段:

local s = s .. "hello"

and assume that s already contains a huge string (say, 10MB). 并假设s已经包含一个巨大的字符串(比方说,10MB)。 Executing that statement creates a new string (10MB + 5 characters) and discards the old one. 执行该语句会创建一个新字符串(10MB + 5个字符)并丢弃旧字符串。 So you have just created a 10MB dead object for the garbage collector to cope with. 所以你刚刚为垃圾收集器创建了一个10MB的死对象来应对。 If you do this repeatedly you end up hogging the garbage collector. 如果你反复这样做,你最终会占用垃圾收集器。 This is the real problem with .. and this is the typical use case where it is necessary to collect all the pieces of the final string in a table and to use table.concat on it: this won't avoid the generation of garbage (all the pieces will be garbage after the call to table.concat ), but you will greatly reduce unnecessary garbage. 这是真正的问题..这是典型的使用情况下,有必要收集,最后一个字符串的所有片段在表中,并使用table.concat就可以了:这将无法避免垃圾的产生(调用table.concat后所有的部分都将是垃圾,但是你会大大减少不必要的垃圾。


Conclusions 结论

  • Use .. whenever you concatenate few, possibly short, strings, or you are not in a tight loop. 每当你连接几个,可能很短的字符串,或者你没有紧密循环时,请使用.. In this case table.concat could give you worse performance because: 在这种情况下, table.concat会给你带来更差的性能,因为:
    • you must create a table (which usually you would throw away); 你必须创建一个表(通常你会扔掉);
    • you have to call the function table.concat (the function call overhead impacts performance more than using the built-in .. operator a few times). 你必须调用函数table.concat (函数调用开销比使用内置的..运算符多次影响性能)。
  • Use table.concat , if you need to concatenate many strings, especially if one or more of the following conditions are met: 如果需要连接多个字符串,请使用table.concat ,尤其是在满足以下一个或多个条件时:
    • you must do it in subsequent steps (the .. optimization works only inside the same expression); 你必须在后续步骤中执行它( ..优化只在同一个表达式中起作用);
    • you are in a tight loop; 你处于紧张的环境中;
    • the strings are large (say, several kBs or more). 字符串很大(比如几个或更多)。

Note that these are just rules of thumb. 请注意,这些只是经验法则。 Where performance is really paramount you should profile your code. 如果性能真的至关重要,那么您应该对代码进行分析。

Anyway Lua is quite fast compared with other scripting languages when dealing with strings, so usually you don't need to care so much. 无论如何Lua在处理字符串时与其他脚本语言相比要快得多,所以通常你不需要太在意。

In your example, whether the .. operator does optimization is hardly a problem for the performance, you don't have to worry about memory or CPU. 在您的示例中, ..运算符是否进行优化对于性能来说几乎不是问题,您不必担心内存或CPU。 And there's table.concat for concatenating many strings. 还有用于连接多个字符串的table.concat (See Programming in Lua ) for the use of table.concat . (请参阅Lua中的编程 )以了解table.concat

Back to your question, in this piece of code 回到你的问题,在这段代码中

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

Lua allocates only one new string, check out this loop from Lua's relevant source in luaV_concat : 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 */

You can see that Lua concatenate n strings in this loop but only pushes back to the stack one string in the end, which is the result string. 你可以看到Lua在这个循环中连接了n字符串,但是最后只将一个字符串推回到堆栈,这是结果字符串。

BTW, I know about table:concat().顺便说一句,我知道 table:concat()。 This has the added overhead of creating a table so I guess it won't be beneficial in all use cases.这增加了创建表的开销,所以我想它不会在所有用例中都有益。

In this particular use case (and similar ones), you could consider reusing a table if you're concerned with creating lots of garbage tables:在这个特定的用例(和类似的用例)中,如果您担心创建大量垃圾表,则可以考虑重用表:

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
...

you could even generalize this to a "concat" utility:您甚至可以将其概括为“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