我必须执行以下代码:
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明智的)。
(BTW,我知道table:concat(),这会增加创建表的开销,所以我想它不会在所有用例中都有好处。)
额外问题:
如果Lua没有优化“..”操作符,那么定义一个C函数来连接字符串(例如utils.concat(dir, "/", base, ".", extension)
)是否是个好主意?
3条答案
按热度按时间moiiocjp1#
尽管Lua对
..
的使用进行了简单的优化,但是在紧密循环中使用它时仍然应该小心,特别是在连接非常大的字符串时,因为这将产生大量垃圾,从而影响性能。连接多个字符串的最佳方法是使用
table.concat
。table.concat
允许您使用一个表作为所有要连接的字符串的临时缓冲区,并且仅在将字符串添加到缓冲区后才执行连接,如下面这个愚蠢的示例所示:通过分析以下脚本的反汇编字节码,可以看到
..
的简单优化:luac -l -p lua_06.lua
的输出如下(对于Lua 5.2.2-* 编辑:Lua 5.3.6 * 中也输出相同字节码):您可以看到,虽然脚本中使用了许多
..
操作符,但只生成了一个CONCAT
操作码。要完全理解什么时候使用
table.concat
,你必须知道Lua字符串是 * 不可变的 *,这意味着当你试图连接两个字符串时,你实际上是在创建一个新的字符串(除非结果字符串已经被解释器接收,但这通常是不可能的)。并假设
s
已经包含了一个巨大的字符串(比如说,执行该语句将创建一个新字符串(10MB +5个字符)并丢弃旧的。所以你刚刚创建了一个10MB的死对象供垃圾收集器处理。如果你反复这样做,你最终会霸占垃圾收集器。这是..
的真正问题,也是典型的用例,需要将最终字符串的所有片段收集到一个表中,然后在表中使用table.concat
:这并不能避免垃圾的产生(调用table.concat
之后,所有的片段 * 都将是 * 垃圾),但是您将大大减少 * 不必要的 * 垃圾。结论
..
。在这种情况下,table.concat
会给你带来 * 更差的 * 性能,因为:table.concat
(函数调用开销对性能的影响要比多次使用内置的..
运算符更大)。table.concat
:..
优化仅在同一表达式内有效);注意,这些只是经验法则,在性能真正重要的地方,你应该分析你的代码。
无论如何,与其他脚本语言相比,Lua在处理字符串时速度相当快,所以通常不需要太在意。
gudnpqoy2#
在您的示例中,
..
操作符是否进行优化对性能来说几乎不是问题,您不必担心内存或CPU。还有table.concat
用于连接许多字符串。(参见Programming in Lua)了解table.concat
的用法。回到你的问题在这段代码中
Lua只分配一个新字符串,请查看
luaV_concat
中Lua相关源代码的循环:您可以看到,Lua在这个循环中连接
n
字符串,但最后只将一个字符串(即结果字符串)推回堆栈。jgzswidk3#
顺便说一句,我知道table:concat(),这会增加创建表的开销,所以我想它不会对所有用例都有好处。
在这个特定的用例(以及类似的用例)中,如果您担心创建大量垃圾表,可以考虑重用表:
你甚至可以把它归纳为一个“concat”实用程序: