在 Ruby 中使用单引号和双引号是否能提高性能?

你知道在 ruby 中使用双引号而不是单引号是否会以任何有意义的方式降低 ruby 1.8和1.9的性能吗。

所以如果我打字

question = 'my question'

是不是比

question = "my question"

我猜想 Ruby 在遇到双引号时会试图找出是否需要对某些内容进行求值,并且可能会花费一些周期来做到这一点。

26019 次浏览

总结: 没有速度差异; 这个 非常棒的协作式 Ruby 风格指南建议保持一致。我现在使用 'string',除非需要插值(指南中的选项 A)并且喜欢它,但是您通常会看到更多使用 "string"的代码。

详情:

从理论上讲,当代码为 解析时,它可能会有所不同,但是不仅不应该在一般情况下关心解析时间(与执行时间相比可以忽略不计) ,而且在这种情况下也不会发现显著的差异。

重要的是,当是得到 处决它将是 一模一样

对此进行基准测试只能说明对 Ruby 的工作原理缺乏理解。在这两种情况下,字符串都将被解析为 tSTRING_CONTENT(参见 parse.y的源头)。换句话说,在创建 'string'"string"时,CPU 将执行完全相同的操作。完全相同的部分会以完全相同的方式翻转。对此进行基准测试只会显示由于其他因素(GC 启动等)而不显著的差异; 请记住,在这种情况下不可能有任何差异!像这样的微型基准很难做好。看到我的宝石 fruity为这一体面的工具。

请注意,如果有形式 "...#{...}..."的插值,这将被解析为 tSTRING_DBEG#{...}中每个表达式的一束 tSTRING_DVAR和最终的 tSTRING_DEND。但是,这只是在有插值的情况下,而这并不是 OP 的目的。

我过去常常建议你在任何地方使用双引号(这样以后实际添加 #{some_var}会更容易) ,但是现在我使用单引号,除非我需要插值,\n等等。.我喜欢它的视觉效果,而且它稍微显式一些,因为不需要解析字符串来查看它是否包含任何表达式。

这当然取决于实现,但是解释器的扫描部分应该只查看每个字符一次。它只需要一个额外的状态(或者一组可能的状态)和转换来处理 # {}块。< br > < br > 在基于表的扫描程序中,这将是一个单独的查找来确定转换,并且无论如何都会对每个字符进行查找。< br > < br > 当解析器获得扫描器输出时,已经知道它必须在块中执行 eval 代码。所以开销实际上只是处理 # {}块的扫描器/解析器中的内存开销,这两种方式都需要支付。

除非我遗漏了什么(或者记错了编译器的构造细节) ,这当然也是有可能的:)

$ ruby -v
ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-darwin11.0.0]


$ cat benchmark_quotes.rb
# As of Ruby 1.9 Benchmark must be required
require 'benchmark'


n = 1000000
Benchmark.bm(15) do |x|
x.report("assign single") { n.times do; c = 'a string'; end}
x.report("assign double") { n.times do; c = "a string"; end}
x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
x.report("concat double") { n.times do; "a string " + "b string"; end}
end


$ ruby benchmark_quotes.rb


user     system      total        real
assign single     0.110000   0.000000   0.110000 (  0.116867)
assign double     0.120000   0.000000   0.120000 (  0.116761)
concat single     0.280000   0.000000   0.280000 (  0.276964)
concat double     0.270000   0.000000   0.270000 (  0.278146)

注意: 我对此进行了更新,使其能够与更新的 Ruby 版本一起工作,并清理了头部,在更快的系统上运行基准测试。

这个答案省略了一些关键点。特别是这些关于 插值和使用单引号和双引号时 在性能上没有显著差异的原因的答案。

没有区别-除非您使用 #{some_var}样式的字符串插值。但是,只有真正这样做,才能获得高性能。

修改自 Zetetic 的的例子:

require 'benchmark'
n = 1000000
Benchmark.bm do |x|
x.report("assign single") { n.times do; c = 'a string'; end}
x.report("assign double") { n.times do; c = "a string"; end}
x.report("assign interp") { n.times do; c = "a #{n} string"; end}
x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
x.report("concat double") { n.times do; "a string " + "b string"; end}
x.report("concat interp") { n.times do; "a #{n} string " + "b #{n} string"; end}
end

输出

               user       system     total    real
assign single  0.370000   0.000000   0.370000 (  0.374599)
assign double  0.360000   0.000000   0.360000 (  0.366636)
assign interp  1.540000   0.010000   1.550000 (  1.577638)
concat single  1.100000   0.010000   1.110000 (  1.119720)
concat double  1.090000   0.000000   1.090000 (  1.116240)
concat interp  3.460000   0.020000   3.480000 (  3.535724)

不过,没有人碰巧测量过连接与插值的关系:

$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9.6.2]
$ cat benchmark_quotes.rb
require 'benchmark'
n = 1000000
Benchmark.bm do |x|
x.report("assign single") { n.times do; c = 'a string'; end}
x.report("assign double") { n.times do; c = "a string"; end}
x.report("assign interp") { n.times do; c = "a string #{'b string'}"; end}
x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
x.report("concat double") { n.times do; "a string " + "b string"; end}
end


$ ruby -w benchmark_quotes.rb
user     system      total        real
assign single  2.600000   1.060000   3.660000 (  3.720909)
assign double  2.590000   1.050000   3.640000 (  3.675082)
assign interp  2.620000   1.050000   3.670000 (  3.704218)
concat single  3.760000   1.080000   4.840000 (  4.888394)
concat double  3.700000   1.070000   4.770000 (  4.818794)

特别注意 assign interp = 2.62concat single = 3.76。 作为锦上添花,我还发现插值比 'a' + var + 'b'更具可读性,特别是在空格方面。

单引号可以比双引号快一点,因为 lexer 不需要检查 #{}插值标记。视乎实施情况等。请注意,这是一个解析时成本,而不是运行时成本。

也就是说,实际的问题是,使用双引号字符串是否会“以任何有意义的方式降低性能”,对于这个问题,答案是决定性的“否”。性能上的差异非常小,与任何实际的性能问题相比都是微不足道的。别浪费时间了。

当然,实际的插值是另一回事。 'foo'将比 "#{sleep 1; nil}foo"快几乎整整1秒。

我想我应该加上1.8.7和1.9.2的比较。我把它们运行了几次。方差大约是 +-0.01。

require 'benchmark'
n = 1000000
Benchmark.bm do |x|
x.report("assign single") { n.times do; c = 'a string'; end}
x.report("assign double") { n.times do; c = "a string"; end}
x.report("assign interp") { n.times do; c = "a #{n} string"; end}
x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
x.report("concat double") { n.times do; "a string " + "b string"; end}
x.report("concat interp") { n.times do; "a #{n} string " + "b #{n} string"; end}
end

Ruby 1.8.7(2010-08-16 patchlevel 302)[ x86 _ 64-linux ]

assign single  0.180000   0.000000   0.180000 (  0.187233)
assign double  0.180000   0.000000   0.180000 (  0.187566)
assign interp  0.880000   0.000000   0.880000 (  0.877584)
concat single  0.550000   0.020000   0.570000 (  0.567285)
concat double  0.570000   0.000000   0.570000 (  0.570644)
concat interp  1.800000   0.010000   1.810000 (  1.816955)

Ruby 1.9.2 p0(2010-08-18版本29036)[ x86 _ 64-linux ]

  user          system      total      real
assign single  0.140000   0.000000   0.140000 (  0.144076)
assign double  0.130000   0.000000   0.130000 (  0.142316)
assign interp  0.650000   0.000000   0.650000 (  0.656088)
concat single  0.370000   0.000000   0.370000 (  0.370663)
concat double  0.370000   0.000000   0.370000 (  0.370076)
concat interp  1.420000   0.000000   1.420000 (  1.412210)

这两个方向没有显著差异,只有巨大的差异才会产生重要影响。

除了确定在计时方面存在实际问题的时候,优化程序员的可维护性。

机器时间的成本非常非常小。程序员编写代码和维护代码的时间成本是巨大的。

如果这意味着代码更难维护,那么为了节省几秒钟甚至几分钟的运行时间而进行优化又有什么用呢?

选择一个样式并坚持使用它,但是 没有会根据统计上无关紧要的毫秒运行时来选择这个样式。

~ > ruby -v
jruby 1.6.7 (ruby-1.8.7-p357) (2012-02-22 3e82bc8) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_37) [darwin-x86_64-java]
~ > cat qu.rb
require 'benchmark'


n = 1000000
Benchmark.bm do |x|
x.report("assign single") { n.times do; c = 'a string'; end}
x.report("assign double") { n.times do; c = "a string"; end}
x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
x.report("concat double") { n.times do; "a string " + "b string"; end}
end
~ > ruby qu.rb
user     system      total        real
assign single  0.186000   0.000000   0.186000 (  0.151000)
assign double  0.062000   0.000000   0.062000 (  0.062000)
concat single  0.156000   0.000000   0.156000 (  0.156000)
concat double  0.124000   0.000000   0.124000 (  0.124000)

双引号的键击次数是单引号的两倍。我总是赶时间。我用单引号。:)是的,我认为这是一个“业绩收益”。:)

有一个你们都错过了。

这里,医生

试试这个

require 'benchmark'
mark = <<EOS
a string
EOS
n = 1000000
Benchmark.bm do |x|
x.report("assign here doc") {n.times do;  mark; end}
end

它给了我

`asign here doc  0.141000   0.000000   0.141000 (  0.140625)`

还有

'concat single quotes  1.813000   0.000000   1.813000 (  1.843750)'
'concat double quotes  1.812000   0.000000   1.812000 (  1.828125)'

所以它肯定比连写和写所有这些推杆要好。

我希望看到 Ruby 更多地沿着文档操作语言的思路进行教学。

毕竟,我们在 Rails、 Sinatra 和运行测试中不都是这样做的吗?

我修改了蒂姆 · 斯诺怀特的答案。

require 'benchmark'
n = 1000000
attr_accessor = :a_str_single, :b_str_single, :a_str_double, :b_str_double
@a_str_single = 'a string'
@b_str_single = 'b string'
@a_str_double = "a string"
@b_str_double = "b string"
@did_print = false
def reset!
@a_str_single = 'a string'
@b_str_single = 'b string'
@a_str_double = "a string"
@b_str_double = "b string"
end
Benchmark.bm do |x|
x.report('assign single       ') { n.times do; c = 'a string'; end}
x.report('assign via << single') { c =''; n.times do; c << 'a string'; end}
x.report('assign double       ') { n.times do; c = "a string"; end}
x.report('assing interp       ') { n.times do; c = "a string #{'b string'}"; end}
x.report('concat single       ') { n.times do; 'a string ' + 'b string'; end}
x.report('concat double       ') { n.times do; "a string " + "b string"; end}
x.report('concat single interp') { n.times do; "#{@a_str_single}#{@b_str_single}"; end}
x.report('concat single <<    ') { n.times do; @a_str_single << @b_str_single; end}
reset!
# unless @did_print
#   @did_print = true
#   puts @a_str_single.length
#   puts " a_str_single: #{@a_str_single} , b_str_single: #{@b_str_single} !!"
# end
x.report('concat double interp') { n.times do; "#{@a_str_double}#{@b_str_double}"; end}
x.report('concat double <<    ') { n.times do; @a_str_double << @b_str_double; end}
end

结果:

jruby 1.7.4 (1.9.3p392) 2013-05-16 2390d3b on Java HotSpot(TM) 64-Bit Server VM 1.7.0_10-b18 [darwin-x86_64]
user     system      total        real
assign single         0.220000   0.010000   0.230000 (  0.108000)
assign via << single  0.280000   0.010000   0.290000 (  0.138000)
assign double         0.050000   0.000000   0.050000 (  0.047000)
assing interp         0.100000   0.010000   0.110000 (  0.056000)
concat single         0.230000   0.010000   0.240000 (  0.159000)
concat double         0.150000   0.010000   0.160000 (  0.101000)
concat single interp  0.170000   0.000000   0.170000 (  0.121000)
concat single <<      0.100000   0.000000   0.100000 (  0.076000)
concat double interp  0.160000   0.000000   0.160000 (  0.108000)
concat double <<      0.100000   0.000000   0.100000 (  0.074000)


ruby 1.9.3p429 (2013-05-15 revision 40747) [x86_64-darwin12.4.0]
user     system      total        real
assign single         0.100000   0.000000   0.100000 (  0.103326)
assign via << single  0.160000   0.000000   0.160000 (  0.163442)
assign double         0.100000   0.000000   0.100000 (  0.102212)
assing interp         0.110000   0.000000   0.110000 (  0.104671)
concat single         0.240000   0.000000   0.240000 (  0.242592)
concat double         0.250000   0.000000   0.250000 (  0.244666)
concat single interp  0.180000   0.000000   0.180000 (  0.182263)
concat single <<      0.120000   0.000000   0.120000 (  0.126582)
concat double interp  0.180000   0.000000   0.180000 (  0.181035)
concat double <<      0.130000   0.010000   0.140000 (  0.128731)

我也认为单引号字符串可能会更快地解析 Ruby。似乎并非如此。

无论如何,我认为上述基准测量错误的东西,虽然。 显而易见,两个版本都将被解析成相同的内部字符串表示,以便得到哪个解析速度更快的答案,我们不应该用字符串变量来衡量性能,而应该用 Ruby 解析字符串的速度。

generate.rb:
10000.times do
('a'..'z').to_a.each {|v| print "#{v}='This is a test string.'\n" }
end


#Generate sample ruby code with lots of strings to parse
$ ruby generate.rb > single_q.rb
#Get the double quote version
$ tr \' \" < single_q.rb > double_q.rb


#Compare execution times
$ time ruby single_q.rb


real    0m0.978s
user    0m0.920s
sys     0m0.048s
$ time ruby double_q.rb


real    0m0.994s
user    0m0.940s
sys     0m0.044s

重复跑步似乎没有多大区别。解析字符串的任何一个版本仍然需要大致相同的时间。

我尝试了以下方法:

def measure(t)
single_measures = []
double_measures = []
double_quoted_string = ""
single_quoted_string = ''
single_quoted = 0
double_quoted = 0


t.times do |i|
t1 = Time.now
single_quoted_string << 'a'
t1 = Time.now - t1
single_measures << t1


t2 = Time.now
double_quoted_string << "a"
t2 = Time.now - t2
double_measures << t2


if t1 > t2
single_quoted += 1
else
double_quoted += 1
end
end
puts "Single quoted did took longer in #{((single_quoted.to_f/t.to_f) * 100).round(2)} percent of the cases"
puts "Double quoted did took longer in #{((double_quoted.to_f/t.to_f) * 100).round(2)} percent of the cases"


single_measures_avg = single_measures.inject{ |sum, el| sum + el }.to_f / t
double_measures_avg = double_measures.inject{ |sum, el| sum + el }.to_f / t
puts "Single did took an average of #{single_measures_avg} seconds"
puts "Double did took an average of #{double_measures_avg} seconds"
puts "\n"
end
both = 10.times do |i|
measure(1000000)
end

这些是产出:

1.

Single quoted did took longer in 32.33 percent of the cases
Double quoted did took longer in 67.67 percent of the cases
Single did took an average of 5.032084099982639e-07 seconds
Double did took an average of 5.171539549983464e-07 seconds

2.

Single quoted did took longer in 26.9 percent of the cases
Double quoted did took longer in 73.1 percent of the cases
Single did took an average of 4.998066229983696e-07 seconds
Double did took an average of 5.223457359986066e-07 seconds

3.

Single quoted did took longer in 26.44 percent of the cases
Double quoted did took longer in 73.56 percent of the cases
Single did took an average of 4.97640888998877e-07 seconds
Double did took an average of 5.132918459987151e-07 seconds

4.

Single quoted did took longer in 26.57 percent of the cases
Double quoted did took longer in 73.43 percent of the cases
Single did took an average of 5.017136069985988e-07 seconds
Double did took an average of 5.004514459988143e-07 seconds

5.

Single quoted did took longer in 26.03 percent of the cases
Double quoted did took longer in 73.97 percent of the cases
Single did took an average of 5.059069689983285e-07 seconds
Double did took an average of 5.028807639983705e-07 seconds

6.

Single quoted did took longer in 25.78 percent of the cases
Double quoted did took longer in 74.22 percent of the cases
Single did took an average of 5.107472039991399e-07 seconds
Double did took an average of 5.216212339990241e-07 seconds

7.

Single quoted did took longer in 26.48 percent of the cases
Double quoted did took longer in 73.52 percent of the cases
Single did took an average of 5.082368429989468e-07 seconds
Double did took an average of 5.076817109989933e-07 seconds

8.

Single quoted did took longer in 25.97 percent of the cases
Double quoted did took longer in 74.03 percent of the cases
Single did took an average of 5.077162969990005e-07 seconds
Double did took an average of 5.108381859991112e-07 seconds

9.

Single quoted did took longer in 26.28 percent of the cases
Double quoted did took longer in 73.72 percent of the cases
Single did took an average of 5.148080479983138e-07 seconds
Double did took an average of 5.165793929982176e-07 seconds

10.

Single quoted did took longer in 25.03 percent of the cases
Double quoted did took longer in 74.97 percent of the cases
Single did took an average of 5.227828659989748e-07 seconds
Double did took an average of 5.218296609988378e-07 seconds

如果我没有弄错的话,在我看来,两者所花的时间大致相同,即使单引号在大多数情况下稍微快一点。