2009-12-02 24 views
115

你知道如果在红宝石中使用双引号而不是单引号,会降低红宝石1.8和1.9中任何有意义的性能。在红宝石中使用单引号与双引号是否有性能提升?

,所以如果我型

question = 'my question' 

是它的速度比

question = "my question" 

我想象红宝石试图找出是否有什么需要进行评估,当它遇到双引号,可能花费数个周期这样做。

+17

运行它50万次,看看。有可能,您的网站没有获得足够的流量。过早优化通常不值得。 – ceejayoz

+56

为什么那么多人期望ruby只能用于web编程? – johannes

+17

我不会考虑这个过早的优化。自从你的应用程序完成并且针对单一或双重优化后返回的更多“最佳实践”将是一件非常头疼的事情。 – Omar

回答

83
$ 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版本工作,清理标题,并在更快的系统上运行基准测试。

此答案省略了一些要点。当使用单引号和双引号时,请特别参考有关interpolation以及原因there is no significant difference in performance的其他解答。

+1

没有插值? –

+0

请参阅下面的madlep的回答 – zetetic

+0

我是否正确解释结果?使用双引号分配实际上比单个分配更快?怎么会这样? – randomguy

93

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

详情:

理论上,它可以有所作为,当你的代码是解析,但不是唯一的你应该不会在意总体分析时(可以忽略不计相比,执行时间),你赢了”在这种情况下能够找到显着的差异。

重要的是,当得到执行它会是完全一样

基准测试只显示对Ruby的工作原理缺乏了解。在这两种情况下,字符串都将被解析为tSTRING_CONTENT(请参阅the source in parse.y)。换句话说,创建'string'"string"时,CPU将执行完全相同的操作。完全相同的位将翻转完全相同的方式。基准化这只会显示不显着的差异和由于其他因素(GC踢入等)的差异;请记住,在这种情况下不会有任何区别!像这样的微基准很难得到正确的结果。看到我的宝石fruity为此一个体面的工具。

注意,如果存在形式"...#{...}..."的插值,这被解析到一个tSTRING_DBEG,用于#{...}和最终tSTRING_DEND的每个表达式一堆tSTRING_DVAR。但是,只有在有插值的情况下,这不是OP的内容。

我曾经建议你到处使用双引号(使得它更容易实际添加#{some_var}以后),但我现在用单引号,除非我需要插值,\n,等等。我喜欢在视觉上它和它的略更明确,因为不需要解析字符串来查看它是否包含任何表达式。

+3

似乎更重要的是分钟的性能差异。双引号是! –

+0

感谢您指点我的答案。你能否澄清你为什么说基准测试是误导性的?我同意这些差异可能可以忽略不计,但在某些方面是否是基准错误? (有人已经强调'#{n}'会做数字转换)。它没有显示解析中的差异吗? – PhilT

+1

感谢您链接到风格指南。不敢相信我以前没有遇到过。 – PhilT

0

这取决于具体实现,当然是可以的,但是解释器的扫描部分应该只查看每个字符一次。它只需要一个额外的状态(或可能的一组状态)和转换来处理#{}块。

在一个基于表格的扫描器中,它将成为一个单一的查找来确定转换,反正会发生在每个字符上。

解析器获取扫描仪输出时,已经知道它必须在块中对代码进行评估。因此,开销只是扫描器/解析器中用于处理#{}块的内存开销,您无论采用哪种方式都会付出代价。

除非我失去了一些东西(或记错编译器构造的细节),这也是绝对有可能:)

16

没有区别 - 除非您使用#{some_var}样式字符串插值。但是如果你真的这么做了,你只会得到性能的提升。

Zetetic's例如修改:

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) 
+0

有趣的插值看起来更贵一点,这是1.8吗?很高兴看到1.9是否改变任何东西 – zetetic

+0

zetetic - yup。这是针对Ruby 1.8.7 – madlep

+1

Interp版本是插值和连接,并将一个数字转换为一个字符串两次,如果结果相同,插值会胜出,请参阅https://gist.github.com/810463。真正需要关注的是比单引号或双引号更多的关注。 –

33

没有一个发生测量级联VS内插虽然:

$ 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.62 VS concat single = 3.76。 作为锦上添花,我还发现插值比'a' + var + 'b'更具可读性,特别是在空间方面。

+0

+1。这是唯一的比较苹果和苹果的插值基准 –

+1

基准测试可能会引起误解;请参阅我的答案,为什么。对于级联和插值之间的比较,应该是b显然,插值不能比连接慢。无论如何,这不是问题的一部分! –

+0

您可以添加<<进行此测试吗? – Nick

13

单引号可能会比双引号稍快,因为词法分析器不必检查#{}插值标记。取决于实施情况等。请注意,这是一个分析时间成本,而不是运行时成本。

也就是说,实际的问题是使用双引号字符串“是否会以任何有意义的方式降低性能”,答案是决定性的“否”。性能差异非常小,与任何真正的性能问题相比,它是完全不重要的。不要浪费你的时间。

当然,实际插值是一个不同的故事。 'foo'几乎比"#{sleep 1; nil}foo"快1秒。

+4

+1因为注意到编译时的成本并非运行时,所以上述基于基准的高回答的答案是误导性的。 – nohat

+0

“这是一个分析时间成本,而不是运行时成本。”是关键短语。 –

8

想到我会添加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 

红宝石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) 

红宝石1.9.2p0(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) 
+0

+1查看方差。 –

+0

Interp必须对字符串转换进行编号。请参阅https://gist.github.com/810463。 –

+0

查看我的答案*为什么*你得到这些数字。 –

3

有一个在没有显著差异任何方向。它必须是巨大的重要。

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

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

如果这意味着代码难以维护,优化可以节省几秒甚至几十分钟的运行时间,这有什么好处?

选择与风格,并坚持下去,但这样做挑基于运行时的统计显着毫秒风格。

0
~ > 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) 
7

双引号的键击次数比单引号多一倍。我总是很匆忙。我使用单引号。 :)是的,我认为这是一个“性能增益”。 :)

+0

为什么双引号需要重击两次?他们都是由一个单一的关键。此外,许多IDE会自动添加结束报价。 –

+2

即使IDE自动关闭报价,双引号仍然需要100%以上的按键罢工。 ;-) –

+0

马特Dressel:双引号需要键击次数的两倍,因为你还需要敲击shift键。哦::)以防万一你在我原来的评论中错过了它。 :) 有线钥匙需要更多的努力,可以说更多的时间来执行。 :) – aqn

0

有一个你都错过了。

HERE文档

试试这个

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)' 

所以它肯定比CONCAT更好的和写所有的这些看跌期权。

我想看到Ruby更多地沿着文档操纵语言的方向教授。

毕竟,我们不是真的在Rails,Sinatra中运行测试吗?

0

我改编了Tim Snowhite的回答。

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) 
1

我也认为,单引号的字符串可能会更快解析为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 

重复运行似乎没有太大的差别。解析字符串的任何版本仍然需要几乎相同的时间。

0

我尝试以下:

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 
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 
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 

如果我没有错,在我看来,无论采取大致相同的时间,即使单报价在大多数情况下稍快。