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


126

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

所以如果我输入

question = 'my question'

比它快吗

question = "my question"

我想像一下,ruby会尝试找出某些事物在遇到双引号时是否需要评估,并可能花费一些周期来进行评估。


17
运行它五百万次,看看。很有可能,您的网站没有足够的流量来解决问题。过早的优化通常是不值得的。
ceejayoz

60
为什么这么多人期望ruby仅用于Web编程?
johannes

17
我不会考虑这种过早的优化。更多的是“最佳实践”,因为在应用程序完成后返回并针对单个或两个进行优化将是一个巨大的麻烦。
奥马尔

7
对我来说,这只是样式:在其他情况下,我对“静态”字符串使用单引号,对双qoutes(或其他内插字符串)使用双引号。
tig 2010年

3
@Baddie:如果您要优化一个不存在的问题,这是过早的优化。
安迪·莱斯特

Answers:


86
$ 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版本一起使用,并清理了标头,并在更快的系统上运行了基准测试。

这个答案省略了一些关键点。尤其要查看有关插值的其他答案,以及使用单引号和双引号时性能没有显着差异的原因。


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

显然是的,尽管差别很小。至于为什么-击败了我。
zetetic 2010年

如果考虑到编译时间和执行时间,该基准将更具吸引力。
nohat 2011年

9
测得的差异没有意义。只是顺序(由于垃圾收集)可以起重要作用。'"被解析为同一事物之间,它们之间没有运行时差异。
马克-安德烈·Lafortune

104

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

细节:

从理论上讲,它在解析代码时会有所不同,但不仅您不应该一般地关心解析时间(与执行时间相比可以忽略不计),而且在这种情况下您将找不到明显的区别。

重要的是,执行时将完全相同

进行基准测试仅表明缺乏对Ruby工作原理的理解。在这两种情况下,字符串都将被解析为tSTRING_CONTENT(请参见中的源代码parse.y)。换句话说,创建'string'或时,CPU将经历完全相同的操作"string"。完全相同的位将以完全相同的方式翻转。以此为基准只能显示不重要的差异,并且是由于其他因素(GC启动等)引起的;请记住,在这种情况下没有任何区别!像这样的微观基准很难正确。看我的宝石fruity为这个体面的工具。

请注意,如果存在形式的内插,则将其"...#{...}..."解析为a tSTRING_DBEG,一束tSTRING_DVAR对于每个in中的表达式#{...}以及一个finaltSTRING_DEND。但是,只有在存在插值的情况下,OP才不会这样做。

我曾经建议您在所有地方都使用双引号(这样#{some_var}以后可以更容易地添加它),但是现在我使用单引号,除非需要插值\n等,...我在视觉上喜欢它,并且它稍微更明确了,因为没有需要分析字符串以查看它是否包含任何表达式。


3
微小的性能差异似乎更为重要。是双引号!
Venkat D.

感谢您指出我的回答。您能否阐明为什么说基准测试会产生误导?我同意这些差异可能微不足道,但基准在某种程度上是否错误?(有人已经强调过#{n}将进行数字转换)。它没有显示差异吗?
PhilT 2012年

1
感谢您链接到样式指南。不敢相信我以前没有遇到过。
PhilT 2012年

1
您的答案中提到的样式指南已更新,建议采用一致的样式(无论是单引号还是双引号),并指出双引号的字符串在Ruby社区中更为普遍。
philtr

使用双引号。编程很难。语法本质上很复杂。双引号意味着在使字符串动态化时,永远不要犯错误或浪费时间。使用双引号,您无需担心。
凯尔西·汉南

35

虽然没有人测量串联与内插的关系:

$ 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.62VS concat single = 3.76。锦上添花,我还发现插值比'a' + var + 'b'空格更易读。


+1。这是唯一将苹果与苹果进行比较的插值基准。
马克·托马斯

1
基准测试可能会产生误导;看到我的答案为什么。对于级联和插值之间的比较,很明显插值不能慢于级联。无论如何,这实际上不是问题的一部分!
马克-安德烈·Lafortune

您可以在此测试中添加<<吗?
尼克,

16

没什么区别-除非您使用#{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)

有趣。插值看起来更昂贵。是1.8吗?很高兴看到1.9是否有任何改变。
zetetic

zetetic-是的 这是针对Ruby 1.8.7
madlep

1
插值版本是插值和级联,再加上一个数字两次转换为字符串。如果您使结果相同,则插值将获胜。见gist.github.com/810463。真正的收获是,与单引号或双引号相比,不必担心to_s。
Brian Deterling

仅以此为基准进行测试可能会产生误导,并显示出对Ruby工作方式的误解。看我的答案。
马克-安德烈·Lafortune

13

单引号可能比双引号稍微快一点,因为词法分析器不必检查#{}插值标记。取决于实现方式等。请注意,这是解析时成本,而不是运行时成本。

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

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


4
+1表示费用是在编译时而不是在运行时,因此上面基于基准的高度投票的答案具有误导性。
nohat 2011年

“这是一个解析时成本,而不是运行时成本。” 是关键短语。
Tin Man

9

双引号输入的击键次数是单引号的两倍。我总是很着急。我使用单引号。:)是的,我认为这是“性能提升”。:)


为什么双引号需要两次击键?它们都由单个键表示。此外,许多IDE会自动添加右引号。
马特·德莱斯特

3
即使IDE自动关闭引号,双引号仍然需要100%更多的击键。;-)
克林特·帕奇

马特·德莱斯特(Matt Dressel):双引号需要两次击键,因为您也需要击中Shift键。哦::)以防万一您在我的原始评论中错过了它。:)有线键需要更多的精力,并且可能需要更多的时间才能执行。:)
2013年

1
有时我出于懒惰而听从这个建议。但是不幸的是,在其他一些语言中,情况恰恰相反(例如,单引号需要Shift +东西,而双引号则是单键)。不幸的是,如果两个使用不同键盘布局的人在同一个项目上工作,其中一个将不得不牺牲一些击键:)
HalilÖzgür16

“我是一个急忙的人”-除非您依次按下Shift和2(或其他键),否则根本不会使用单引号来节省任何时间。
Machisuji '16

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补丁程序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)

Interp必须执行数字到字符串的转换。见gist.github.com/810463
Brian Deterling

看到我为什么要得到这些数字的答案。
马克-安德烈·Lafortune

关于Interp的好点。我只是复制了以前的答案作为我的基础。那会教我。
PhilT 2012年

3

任一方向都没有显着差异。事情要大很多。

除了确定时序确实存在问题之外,请针对程序员的可维护性进行优化。

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

如果可以节省几秒钟的时间,甚至可以节省数千秒的运行时间(甚至数千分钟),那么优化有什么好处呢?

挑选一个风格,坚持下来的,但千万不能挑基于运行时的统计显着毫秒风格。


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

当然,这取决于实现方式,但是解释器的扫描部分只应对每个字符查看一次。它只需要一个附加状态(或可能的状态集)和转换即可处理#{}块。

在基于表的扫描器中,这将是一次查找以确定过渡,并且无论如何都会针对每个字符进行查找。

当解析器获取扫描器输出时,就已经知道它将必须评估该块中的代码。因此,开销实际上只是扫描器/解析器中处理#{}块的内存开销,您可以为这两种方式付费。

除非我丢失了某些内容(或错误地记住了编译器的构造细节),否则当然也可能是这样的:)


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)

0

你们都错过了一个。

这里doc

试试这个

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

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

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)

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

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

如果我没有记错的话,尽管在大多数情况下用单引号括起来稍微快一点,但在我看来这似乎都需要花费大约相同的时间。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.