说明

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

    if jit then
    jit.off()
    end

    -- 随机生成字符串备用
    local randomStringSzie = 200000
    local strTable = {}
    function ()
    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
    17
    test1_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
    17
    test1_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
    71
    if jit then
    jit.off()
    end

    math.randomseed(os.time())

    local strTable = {}
    function ()
    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
    29
    test2_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的大小对各种情况有没有性能影响?