lua三种字符串拼接性能分析
说明
lua中提供了3中字符串拼接函数,测试在日常使用情况下,各种拼接字符串的性能对比。
测试环境
操作系统:Debian GNU/Linux 8
CPU:Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz
内存:64G
lua环境:LuaJIT-2.1.0-beta3 (测试的时候关闭jit)
测试
普通日常使用性能分析
日常拼接字符串的时候一般都是多个已存在的变量,夹杂一些字符串常量进行拼接,例如:
1 | package.cpath = pg.script_dir .. "/bot/lib/?.so;" .. id .. package.cpath |
测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63-- 关闭jit
if jit then
jit.off()
end
-- 随机生成字符串备用
local randomStringSzie = 200000
local strTable = {}
function makeStrTable()
for i=1, randomStringSzie do
strTable[#strTable + 1] = tostring(math.random(1, randomStringSzie*10))
end
end
-- 辅助函数,用来打印时间
function showtime(f)
local start = os.clock()
f()
print(os.clock() - start)
end
local testCount = 50000
-- string.format
function test1_1_0()
io.write("test1_1_0: use string.format: ")
for i=1, testCount do
local str = string.format("%s-string1-%s-string2-%s", strTable[math.random(1, randomStringSzie)],
strTable[math.random(1, randomStringSzie)],
strTable[math.random(1, randomStringSzie)])
end
end
-- ..
function test1_2_0()
io.write("test1_2_0: use ..: ")
for i=1, testCount do
local str = strTable[math.random(1, randomStringSzie)] .. "-string1-" ..
strTable[math.random(1, randomStringSzie)] .. "-string2-" ..
strTable[math.random(1, randomStringSzie)]
end
end
-- table.concat
function test1_3_0()
io.write("test1_3_0: use table.concat: ")
for i=1, testCount do
local str = table.concat({strTable[math.random(1, randomStringSzie)], "-string1-",
strTable[math.random(1, randomStringSzie)], "-string2-",
strTable[math.random(1, randomStringSzie)]})
end
end
makeStrTable()
collectgarbage("collect") -- 防止gc影响,先清除一遍
showtime(test1_1_0)
collectgarbage("collect") -- 防止gc影响,先清除一遍
showtime(test1_2_0)
collectgarbage("collect") -- 防止gc影响,先清除一遍
showtime(test1_3_0)为了避免字符串拼接函数执行先后顺序的影响,三个函数轮流被注释,单独执行5次的测试结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17test1_1_0: use string.format: 0.029643
test1_1_0: use string.format: 0.029626
test1_1_0: use string.format: 0.029654
test1_1_0: use string.format: 0.029586
test1_1_0: use string.format: 0.029663
test1_2_0: use ..: 0.025584
test1_2_0: use ..: 0.025809
test1_2_0: use ..: 0.025492
test1_2_0: use ..: 0.025415
test1_2_0: use ..: 0.025523
test1_3_0: use table.concat: 0.035074
test1_3_0: use table.concat: 0.035227
test1_3_0: use table.concat: 0.035077
test1_3_0: use table.concat: 0.034965
test1_3_0: use table.concat: 0.035284考虑到这边测试的拼接的都是本身就是字符串的变量,日常中会有很多将number拼接的情况,修改测试函数,使用纯number拼接测试一次:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30-- string.format
function test1_1_0()
io.write("test1_1_0: use string.format: ")
for i=1, testCount do
local str = string.format("%d-string1-%d-string2-%d",
math.random(1, randomStringSzie),
math.random(1, randomStringSzie),
math.random(1, randomStringSzie))
end
end
-- ..
function test1_2_0()
io.write("test1_2_0: use ..: ")
for i=1, testCount do
local str = math.random(1, randomStringSzie) .. "-string1-" ..
math.random(1, randomStringSzie) .. "-string2-" ..
math.random(1, randomStringSzie)
end
end
-- table.concat
function test1_3_0()
io.write("test1_3_0: use table.concat: ")
for i=1, testCount do
local str = table.concat({math.random(1, randomStringSzie), "-string1-",
math.random(1, randomStringSzie), "-string2-",
math.random(1, randomStringSzie)})
end
end同样的方式测试5次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17test1_1_0: use string.format: 0.028289
test1_1_0: use string.format: 0.02815
test1_1_0: use string.format: 0.028763
test1_1_0: use string.format: 0.028143
test1_1_0: use string.format: 0.028161
test1_2_0: use ..: 0.031198
test1_2_0: use ..: 0.030967
test1_2_0: use ..: 0.031142
test1_2_0: use ..: 0.031129
test1_2_0: use ..: 0.03098
test1_3_0: use table.concat: 0.038446
test1_3_0: use table.concat: 0.037694
test1_3_0: use table.concat: 0.037729
test1_3_0: use table.concat: 0.03787
test1_3_0: use table.concat: 0.037877测试结论
- 日常使用情况下都不应该使用table.concat函数来进行拼接字符串,因为每次拼接都会有创建table的性能消耗,并且也会带来gc压力
- 如果使用number比较多的情况下的拼接的字符串,使用string.format性能比..好,反之,如果number较少使用..性能更好;测试发现,日常情况下(总拼接参数个数小于8个),如果number数量:string数量<=1的情况下,使用..性能会好点,其他情况下使用string.format
特殊情况——已存在table拼接
将一个已存在的table中的字符串拼接也可以用这三种字符串进行拼接,那table的大小会不会对三种方式性能有影响?
- 测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71if jit then
jit.off()
end
math.randomseed(os.time())
local strTable = {}
function makeStrTable()
for i=1,200000 do
strTable[#strTable + 1] = tostring(math.random(1,10000000))
end
end
local maxCount = 50000
function test2_1(arr, len)
io.write("test2_1: ")
local start = os.clock()
for cnt=1, maxCount do
local str = ""
for i=1, len do
str = string.format("%s%s", str, arr[i])
end
end
print( os.clock() - start)
end
function test2_2(arr, len)
io.write("test2_2: ")
local start = os.clock()
for cnt=1, maxCount do
local str = ""
for i=1, len do
str = str..arr[i]
end
end
print( os.clock() - start)
end
function test2_3(arr, len)
io.write("test2_3: ")
local start = os.clock()
for cnt=1, maxCount do
local str = table.concat(arr)
end
print( os.clock() - start)
end
makeStrTable()
for i = 2, 10 do
local concatTable = {}
for j=1, i do
concatTable[#concatTable + 1] = strTable[math.random(1,200000)]
end
collectgarbage("collect")
test2_1(concatTable, i)
collectgarbage("collect")
test2_2(concatTable, i)
collectgarbage("collect")
test2_3(concatTable, i)
end - 同样为了相互直接的影响,分别注释并测试的测试结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29test2_1: 0.012331
test2_1: 0.018489
test2_1: 0.02484
test2_1: 0.030889
test2_1: 0.03711
test2_1: 0.044915
test2_1: 0.050445
test2_1: 0.057397
test2_1: 0.064486
test2_2: 0.007221
test2_2: 0.011114
test2_2: 0.013879
test2_2: 0.017953
test2_2: 0.021438
test2_2: 0.024831
test2_2: 0.028542
test2_2: 0.033702
test2_2: 0.037664
test2_3: 0.00487
test2_3: 0.005351
test2_3: 0.005765
test2_3: 0.006716
test2_3: 0.007291
test2_3: 0.00796
test2_3: 0.0082739999999999
test2_3: 0.009553
test2_3: 0.010339 - 测试结论
- 在已经存在的table去调用字符串拼接,不论table里面元素多少,都是table.concat快,因为没有了table创建的开销
特殊情况——创建一个table并拼接
一般情况下,都是从某个table选取一些进行拼接,这时候就不能直接用table.concat对已存在的table进行拼接,需要重新构造一个新的table,或者直接使用string.format, .. 进行拼接,那这种情况下,使用哪种性能最好?新构造的table的大小对各种情况有没有性能影响?
- 测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76if jit then
jit.off()
end
math.randomseed(os.time())
local strTable = {}
function makeStrTable()
for i=1,200000 do
strTable[#strTable + 1] = tostring(math.random(1,10000000))
end
end
local maxCount = 50000
function test2_1(arr, len)
io.write("test2_1: len: " .. len .. ": ")
local start = os.clock()
for cnt=1, maxCount do
local str = ""
for i=1, len do
str = string.format("%s%s", str, arr[i])
end
end
print(os.clock() - start)
end
function test2_2(arr, len)
io.write("test2_2: len: " .. len .. ": ")
local start = os.clock()
for cnt=1, maxCount do
local str = ""
for i=1, len do
str = str..arr[i]
end
end
print(os.clock() - start)
end
function test2_3(arr, len)
io.write("test2_3: len: " .. len .. ": ")
local start = os.clock()
for cnt=1, maxCount do
local tab = {}
for i=1, len do
tab[#tab+1] = arr[i]
end
local str = table.concat(tab)
end
print(os.clock() - start)
end
makeStrTable()
for i = 2, 50 do
local concatTable = {}
for j=1, i do
concatTable[#concatTable + 1] = strTable[math.random(1,200000)]
end
collectgarbage("collect")
test2_1(concatTable, i)
collectgarbage("collect")
test2_2(concatTable, i)
collectgarbage("collect")
test2_3(concatTable, i)
end - 因为要测试的比较多,这里直接忽略函数之间的影响,直接获取跑的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147test2_1: len: 2: 0.012323
test2_2: len: 2: 0.007064
test2_3: len: 2: 0.017743
test2_1: len: 3: 0.018373
test2_2: len: 3: 0.010368
test2_3: len: 3: 0.027305
test2_1: len: 4: 0.024554
test2_2: len: 4: 0.013748
test2_3: len: 4: 0.026225
test2_1: len: 5: 0.027159
test2_2: len: 5: 0.015262
test2_3: len: 5: 0.035428
test2_1: len: 6: 0.032727
test2_2: len: 6: 0.018682
test2_3: len: 6: 0.036837
test2_1: len: 7: 0.038253
test2_2: len: 7: 0.021658
test2_3: len: 7: 0.038941
test2_1: len: 8: 0.043715
test2_2: len: 8: 0.024652
test2_3: len: 8: 0.041066
test2_1: len: 9: 0.050002
test2_2: len: 9: 0.028612
test2_3: len: 9: 0.054813
test2_1: len: 10: 0.05554
test2_2: len: 10: 0.031326
test2_3: len: 10: 0.055422
test2_1: len: 11: 0.062763
test2_2: len: 11: 0.036097
test2_3: len: 11: 0.057184
test2_1: len: 12: 0.069464
test2_2: len: 12: 0.040439
test2_3: len: 12: 0.060116
test2_1: len: 13: 0.075878
test2_2: len: 13: 0.044668
test2_3: len: 13: 0.062267
test2_1: len: 14: 0.082757
test2_2: len: 14: 0.049663
test2_3: len: 14: 0.065098
test2_1: len: 15: 0.089878
test2_2: len: 15: 0.052364
test2_3: len: 15: 0.067282
test2_1: len: 16: 0.096886
test2_2: len: 16: 0.055969
test2_3: len: 16: 0.071199
test2_1: len: 17: 0.105697
test2_2: len: 17: 0.058038
test2_3: len: 17: 0.092911
test2_1: len: 18: 0.107923
test2_2: len: 18: 0.063079
test2_3: len: 18: 0.093996
test2_1: len: 19: 0.11523
test2_2: len: 19: 0.067008
test2_3: len: 19: 0.097838
test2_1: len: 20: 0.121543
test2_2: len: 20: 0.070144
test2_3: len: 20: 0.095648
test2_1: len: 21: 0.125822
test2_2: len: 21: 0.074236
test2_3: len: 21: 0.097661
test2_1: len: 22: 0.133389
test2_2: len: 22: 0.079203
test2_3: len: 22: 0.10079
test2_1: len: 23: 0.141318
test2_2: len: 23: 0.084614999999999
test2_3: len: 23: 0.104983
test2_1: len: 24: 0.148233
test2_2: len: 24: 0.091413
test2_3: len: 24: 0.107491
test2_1: len: 25: 0.155905
test2_2: len: 25: 0.093782
test2_3: len: 25: 0.110824
test2_1: len: 26: 0.16504
test2_2: len: 26: 0.105242
test2_3: len: 26: 0.116656
test2_1: len: 27: 0.180588
test2_2: len: 27: 0.107747
test2_3: len: 27: 0.115238
test2_1: len: 28: 0.179846
test2_2: len: 28: 0.111915
test2_3: len: 28: 0.117459
test2_1: len: 29: 0.190444
test2_2: len: 29: 0.122584
test2_3: len: 29: 0.120503
test2_1: len: 30: 0.200754
test2_2: len: 30: 0.125184
test2_3: len: 30: 0.122902
test2_1: len: 31: 0.206196
test2_2: len: 31: 0.132831
test2_3: len: 31: 0.12567
test2_1: len: 32: 0.212643
test2_2: len: 32: 0.13621
test2_3: len: 32: 0.128972
test2_1: len: 33: 0.224638
test2_2: len: 33: 0.142706
test2_3: len: 33: 0.155577
test2_1: len: 34: 0.230299
test2_2: len: 34: 0.151168
test2_3: len: 34: 0.159434
test2_1: len: 35: 0.24495
test2_2: len: 35: 0.156305
test2_3: len: 35: 0.164588
test2_1: len: 36: 0.259361
test2_2: len: 36: 0.174812
test2_3: len: 36: 0.172479
test2_1: len: 37: 0.268614
test2_2: len: 37: 0.174493
test2_3: len: 37: 0.175878
test2_1: len: 38: 0.280801
test2_2: len: 38: 0.181204
test2_3: len: 38: 0.180053
test2_1: len: 39: 0.286958
test2_2: len: 39: 0.189557
test2_3: len: 39: 0.182902
test2_1: len: 40: 0.296747
test2_2: len: 40: 0.190833
test2_3: len: 40: 0.177761
test2_1: len: 41: 0.298487
test2_2: len: 41: 0.198188
test2_3: len: 41: 0.181526
test2_1: len: 42: 0.307086
test2_2: len: 42: 0.201846
test2_3: len: 42: 0.186407
test2_1: len: 43: 0.318794
test2_2: len: 43: 0.208237
test2_3: len: 43: 0.186203
test2_1: len: 44: 0.323743
test2_2: len: 44: 0.221747
test2_3: len: 44: 0.199269
test2_1: len: 45: 0.346715
test2_2: len: 45: 0.233323
test2_3: len: 45: 0.199532
test2_1: len: 46: 0.360915
test2_2: len: 46: 0.236748
test2_3: len: 46: 0.196382
test2_1: len: 47: 0.360939
test2_2: len: 47: 0.236785
test2_3: len: 47: 0.197901
test2_1: len: 48: 0.364207
test2_2: len: 48: 0.243074
test2_3: len: 48: 0.201713
test2_1: len: 49: 0.373341
test2_2: len: 49: 0.25071
test2_3: len: 49: 0.203139
test2_1: len: 50: 0.383352
test2_2: len: 50: 0.262008
test2_3: len: 50: 0.207223 - 测试结论
- 可以看到..的性能一直会高于string.format(这里假设table里面的元素都是字符串的情况下,如果为全数字的情况经过测试string.format性能高于..)
- 当table长度在29的时候(全数字情况下测试为36),table.concat性能最好,这个数字不一定准确,但是可以大致了解下这个分界值,而且使用string.format与..会带来后续gc的性能影响(这个也比较难测),所以可以大致把这个值估计在25(纯数字的情况下,可以稍微再高点),如果超过这个值,就是用table.concat会更优,写起来也会更加方便