最近开始学习Ruby. 据说“Performance”对于C++程序员来说,就像骨头对于狗一样。看到Ruby里连接String有几种不同的方式,心里面就好奇哪种最快。写了个测试程序如下:
代码:
require 'benchmark'
include Benchmark

N=100000
EMPTY=""
S="a"*1000
s=""

bm(1) do |x|
x.report("2-1:") { N.times { S+S } }
x.report("2-2:") { N.times { "#{S}#{S}" } }
x.report("2-3:") { N.times { s=""; s<<S<<S } }
x.report("2-4:") { N.times { s.replace(EMPTY); s<<S<<S } }

x.report("3-1:") { N.times { S+S+S } }
x.report("3-2:") { N.times { "#{S}#{S}#{S}" } }
x.report("3-3:") { N.times { s=""; s<<S<<S<<S } }
x.report("3-4:") { N.times { s.replace(EMPTY); s<<S<<S<<S } }

x.report("4-1:") { N.times { S+S+S+S } }
x.report("4-2:") { N.times { "#{S}#{S}#{S}#{S}" } }
x.report("4-3:") { N.times { s=""; s<<S<<S<<S<<S } }
x.report("4-4:") { N.times { s.replace(EMPTY); s<<S<<S<<S<<S } }
end
测试结果:
user system total real
2
-
1
:
1.016000
0.328000
1.344000
(
1.391000
)
2
-
2
:
1.391000
0.422000
1.813000
(
1.812000
)
2
-
3
:
1.562000
0.313000
1.875000
(
1.907000
)
2
-
4
:
1.094000
0.000000
1.094000
(
1.093000
)
3
-
1
:
2.281000
1.015000
3.296000
(
3.313000
)
3
-
2
:
2.516000
0.641000
3.157000
(
3.187000
)
3
-
3
:
2.406000
0.797000
3.203000
(
3.204000
)
3
-
4
:
1.641000
0.000000
1.641000
(
1.640000
)
4
-
1
:
3.859000
1.859000
5.718000
(
5.781000
)
4
-
2
:
2.328000
0.875000
3.203000
(
3.204000
)
4
-
3
:
2.578000
0.813000
3.391000
(
3.437000
)
4
-
4
:
1.906000
0.015000
1.921000
(
1.922000
)
分析:
1)S+S.....+S
这种方式,实际是调用rb_str_plus(见Ruby 源代码: string.c).而rb_str_plus作的事情主要是:
rb_str_plus(str1, str2)
malloc memory
, length=
str1.length
+
str2.length
copy str1 to new memory
copy str2 to new memory
end
需要拷贝内存的长度:
S+S: 2*S.length
S+S+S: 2*(2*S.length)+S.length= 5*S.length
S+S+S+S:2*(5*S.length)+S.length= 11*S.length
......
2) "#{S}#{S}....#{S}"
没有找到这种方式的源代码。但是因为测试结果和第三种方式类似,实现方式也应该差不多。我猜测因为第三种方式需要“调用”方法(或者是说发送消息),因而比第二种方式稍慢一点。
3) s=""; s<<S<<S.....<<S
这种方式,实际是调用rb_str_concat(见Ruby 源代码: string.c).而rb_str_concat作的事情主要是:
rb_str_concat(str1, str2)
realloc memory, length
=
str1.length
+
str2.length
+
1
copy str2 to new memory
end
需要拷贝内存的长度:
S+S: 1*S.length
S+S+S: 2*S.length
S+S+S+S: 3*S.length
......
需要注意的是:
s=""; 每次都会产生一个新的空字符串
4) s.replace(EMPTY); s<<S<<S<<....S
这种方式,和第三种方式基本相同,除了:
s.replace(EMPTY); 不会每次产生一个新的空字符串。
有趣的是,在Ruby1.8里,String类没有clear方法。在1.9里面才有: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/189314
结论:
1)一般而言,第四种方式最快,第一种方式最慢。
2)避免Ruby分配过多的新String,可以较明显的提高性能。
3)在性能不是特别重要的情况下,第二种方式最好,因为一般比第一种快,而且只需要一条语句。
代码:

























测试结果:













分析:
1)S+S.....+S
这种方式,实际是调用rb_str_plus(见Ruby 源代码: string.c).而rb_str_plus作的事情主要是:





需要拷贝内存的长度:
S+S: 2*S.length
S+S+S: 2*(2*S.length)+S.length= 5*S.length
S+S+S+S:2*(5*S.length)+S.length= 11*S.length
......
2) "#{S}#{S}....#{S}"
没有找到这种方式的源代码。但是因为测试结果和第三种方式类似,实现方式也应该差不多。我猜测因为第三种方式需要“调用”方法(或者是说发送消息),因而比第二种方式稍慢一点。
3) s=""; s<<S<<S.....<<S
这种方式,实际是调用rb_str_concat(见Ruby 源代码: string.c).而rb_str_concat作的事情主要是:




需要拷贝内存的长度:
S+S: 1*S.length
S+S+S: 2*S.length
S+S+S+S: 3*S.length
......
需要注意的是:
s=""; 每次都会产生一个新的空字符串
4) s.replace(EMPTY); s<<S<<S<<....S
这种方式,和第三种方式基本相同,除了:
s.replace(EMPTY); 不会每次产生一个新的空字符串。
有趣的是,在Ruby1.8里,String类没有clear方法。在1.9里面才有: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/189314
结论:
1)一般而言,第四种方式最快,第一种方式最慢。
2)避免Ruby分配过多的新String,可以较明显的提高性能。
3)在性能不是特别重要的情况下,第二种方式最好,因为一般比第一种快,而且只需要一条语句。