Lua中的string连接
Lua中常用的字符串连接有三种:1 直接用..
2 table.concat()
3 string.format()
,但是三者性能表现有很大差距,而且有些情况下还会因为使用方法不当产生巨大的内存开销,有必要研究一下其中的原理。
..连接符
不少有关于Lua性能分析的文章中都会谈到..
连接符,每次都要把..
批判一番,它似乎一无是处。但在这里我要为..
小小辩护一下:
这些文章中往往举出的例子是《Programming in Lua》中的这一段:
1 |
|
想要解释这个问题,必须先了解一下Lua的string机制:当我们创建一个字符串的时候,Lua首先会检查内部是否已经有相同的字符串了,如果有就会直接返回其引用,没有的话才会真正创建。
这样在buff = buff .. line .. "\n"
字符串拼接的时候,Lua先要查找是否有相同的字符串,然后会把后者整个拷贝然后拼接,创建一块新的内存,buff指向这块内存缓存下来。每一次查找都是一次hash,每一次新字符串的创建都涉及到内存的分配,所以这一段代码无论在时间性能还是空间角度都是非常浪费的。
但是问题的根源是Lua特殊的string机制,并不是..
操作符本身;Lua的机制决定了在for循环中去连接字符串并把中间结果保存下来是非常危险的,无论是用的..
还是string.format()。而想要解决这个问题,可以在循环中把新增的字符串缓存到table中,最后在循环外table.concat()。
事实上,..
这个运算符(而不是函数)的实现是非常高效的:
1 |
|
这样一段代码,很多人认为”a” .. “b” .. “c”需要先执行tmp = “a” .. “b”,再执行tmp = tmp .. “c”的操作,这样的操作如果进行很多次的话,其时间复杂度就会平方增长,然而如果我们使用luac -l text.lua
指令查看生成的opcode:
1 |
|
table.concat()
concat内部实现函数是tconcat,如下:
1 |
|
从源码上看,table.concat没有频繁申请内存,只有当写满一个8192的BUFF时,才会生成一个TString,最后生成多个TString时,会有一次内存申请并合并。在大规模字符串合并时,应尽量选择这种方式。
string.format()
string.format本身是一个函数调用,会根据%将字符串分割成多个部分,然后进行格式字符串的扫描和复制,然后拼接出最终结果存储在buffer上。比起..
额外的开销主要来源于格式字符串的扫描和复制。
结论:
对比以上三种拼接方式的原理可知:
1 大多数情况下,使用..
即是最优。单单..
的操作不会产生多余的临时string;
2 当需要反复拼接的时候,table.concat
效率更高。尤其是在类似于for循环中,..
会产生内存的分配或者拷贝,中间会产生很多的临时string,增加额外的gc压力,而table.concat
可以利用luaL_Buffer
大幅减少拼接的次数,避免产生更多的临时string。
3 string.format只适合于处理较为复杂的带格式的字符串,代码运行效率上没什么优势。