尽管 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):
主要(003E40A0 处的 13 条指令)
0+ 参数,8 个插槽,1 个上值,4 个局部变量,5 个常量,0 个函数
1 [3] 加载K 0 -1 ; “你好”
2 [4] 加载K 1 -2 ; “残忍的”
3 [5] 加载K 2 -3 ; “世界”
4 [7] 移动 3 0
5 [7] 加载K 4 -4 ; " "
6 [7] 移动 5 1
7 [7] 加载K 6 -4 ; " "
8 [7] 移动 7 2
9 [7] 连接 3 3 7
10 [9] 获取 4 0 -5 ; _ENV“打印”
11 [9] 移动 5 3
12 [9] 呼叫 4 2 1
13 [9] 返回 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
, 如果您需要连接多个字符串,尤其是满足以下一个或多个条件时:
- 您必须在后续步骤中执行此操作(
..
优化仅在同一表达式内起作用);
- 你在一个紧密的循环中;
- 字符串很大(例如,几 kB 或更多)。
请注意,这些只是经验法则。在性能非常重要的地方,您应该分析您的代码。
无论如何,在处理字符串时,Lua 与其他脚本语言相比是相当快的,所以通常你不需要太在意。