131

Do you know if using double quotes instead of single quotes in ruby decreases performance in any meaningful way in ruby 1.8 and 1.9.

so if I type

question = 'my question'

is it faster than

question = "my question"

I imagine that ruby tries to figure out if something needs to be evaluated when it encounters double quotes and probably spends some cycles doing just that.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
dimus
  • 8,712
  • 10
  • 45
  • 56
  • 17
    Run it a half million times and see. Chances are, your site doesn't get nearly enough traffic to matter. Premature optimisation is generally not worth it. – ceejayoz Dec 02 '09 at 22:59
  • 61
    why do so many people expect ruby only to be used for web programing? – johannes Dec 03 '09 at 23:14
  • 17
    I wouldn't consider this premature optimization. More of a "best practice" since going back after your app is complete and optimizing for either single or double would be a massive headache. – Omar Dec 05 '09 at 00:08
  • 7
    For me it is just style: I use single quotes for 'static' strings and double qoutes (or other interpolated strings) in other cases. – tig Oct 03 '10 at 21:13
  • 4
    @Baddie: It is premature optimization if you are optimizing away a problem that doesn't exist. – Andy Lester Oct 18 '10 at 20:33
  • 2
    Have to admit I asked this question because guys at my work started to recommend to put single quotes over double quotes, and the answers here put the end to it. – dimus Oct 20 '10 at 12:52
  • There is a difference between "premature optimisation" and good habits. An "optimisation" like this is easily maintained as a good habit. When writing optimized code as a habit, it generally doesn't take much longer to implement, and the benefit outweighs the technical debt. – Tjad Clark Aug 04 '22 at 02:17

14 Answers14

110

Summary: no speed difference; this great collaborative Ruby style guide recommends being consistent. I now use 'string' unless interpolation is needed (option A in the guide) and like it, but you will typically see more code with "string".

Details:

Theoretically, it can make a difference when your code is parsed, but not only should you not care about parse time in general (negligible compared to execution time), you won't be able to find a significant difference in this case.

The important thing is that when is gets executed it will be exactly the same.

Benchmarking this only shows a lack of understanding of how Ruby works. In both cases, the strings will get parsed to a tSTRING_CONTENT (see the source in parse.y). In other words, the CPU will go through the exact same operations when creating 'string' or "string". The exact same bits will flip the exact same way. Benchmarking this will only show differences that are not significant and due to other factors (GC kicking in, etc.); remember, there can't be any difference in this case! Micro benchmarks like these are difficult to get right. See my gem fruity for a decent tool for this.

Note that if there is interpolation of the form "...#{...}...", this gets parsed to a tSTRING_DBEG, a bunch of tSTRING_DVAR for the each expression in #{...} and a final tSTRING_DEND. That's only if there is interpolation, though, which is not what the OP is about.

I used to suggest you use double quotes everywhere (makes it easier to actually add that #{some_var} later on), but I now use single quotes unless I need interpolation, \n, etc... I like it visually and it's slightly more explicit, since there's no need to parse the string to see if it contains any expression.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
  • 3
    Seems much more important that the minute performance difference. Double quotes it is! – Venkat D. Mar 07 '11 at 23:25
  • Thanks for pointing me at your answer. Could you clarify why you are saying that benchmarking this is misleading? I agree the differences are probably negligible but is the benchmark in some way wrong? (Someone already highlighted the `#{n}` would be doing number conversion). Is it not showing the differences in parsing?. – PhilT Nov 20 '12 at 21:20
  • 1
    Thanks for linking to the style guide. Can't believe I didn't come across that before. – PhilT Nov 20 '12 at 21:25
  • 1
    The style guide mentioned in your answer [has been updated](https://github.com/bbatsov/ruby-style-guide/#strings) to suggest adopting a consistent style, whether single or double quotes, and indicates that double quoted strings are more prevalent in the Ruby community. – philtr Apr 25 '14 at 18:59
  • Use double quotes. Programming is hard. Syntax is inherently complex. Double quotes means never making a mistake or wasting time with an error when making a string dynamic. With double quotes, you have one less thing to think about. – Kelsey Hannan Oct 30 '19 at 22:32
90
$ 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)

Note: I've updated this to make it work with newer Ruby versions, and cleaned up the header, and run the benchmark on a faster system.

This answer omits some key points. See especially these other answers concerning interpolation and the reason there is no significant difference in performance when using single vs. double quotes.

Community
  • 1
  • 1
zetetic
  • 47,184
  • 10
  • 111
  • 119
  • Am I interpreting the results correctly? Assignment using double quotes is actually faster than single? How can this be? – randomguy Oct 03 '10 at 12:28
  • Apparently yes, though the difference is minor. As to why -- beats me. – zetetic Oct 04 '10 at 00:27
  • This benchmark would be a lot more compelling if it took into account compilation time as well as execution time. – nohat Dec 05 '11 at 03:03
  • 10
    The measured differences are not meaningful. Just the order (because of garbage collection) can make important a difference. There is no runtime difference between `'` and `"` as they are parsed to the same thing. – Marc-André Lafortune Aug 01 '12 at 16:25
34

No one happened to measure concatenation vs interpolation though:

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

Specifically, note assign interp = 2.62 vs concat single = 3.76. As icing on the cake, I also find interpolation to be more readable than 'a' + var + 'b' especially with regard to spaces.

go2null
  • 2,080
  • 1
  • 21
  • 17
Tim Snowhite
  • 3,736
  • 2
  • 23
  • 27
  • +1. This is the only interpolation benchmark that's comparing apples with apples. – Mark Thomas Feb 11 '11 at 12:00
  • 1
    Benchmarking can be misleading; see my answer for why. As for comparison between concatenation and interpolation, it should be obvious that interpolation can not be slower than concatenation. In any case, that is not really part of the question! – Marc-André Lafortune Aug 01 '12 at 16:45
  • Can you add << to this test? – Nick Aug 28 '13 at 13:07
16

No difference - unless you're using #{some_var} style string interpolation. But you only get the performance hit if you actually do that.

Modified from Zetetic's example:

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

output

               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)
Community
  • 1
  • 1
madlep
  • 47,370
  • 7
  • 42
  • 53
  • Interesting. Interpolation looks a bit more expensive. Was this 1.8? It would be nice to see if 1.9 changes anything. – zetetic Dec 04 '09 at 00:20
  • zetetic - yup. This was against Ruby 1.8.7 – madlep Dec 04 '09 at 03:29
  • 1
    The interp version is both interpolating and concatenating plus converting a number to a string twice. Interpolation wins if you make the results the same. See https://gist.github.com/810463. The real takeaway is to worry more about to_s than single or double quotes. – Brian Deterling Feb 03 '11 at 23:37
  • Benchmarking this only can be misleading and shows a misunderstanding of how Ruby works. See my answer. – Marc-André Lafortune Aug 01 '12 at 16:35
13

Single quotes can be very slightly faster than double quotes because the lexer doesn't have to check for #{} interpolation markers. Depending on implementation, etc. Note that this is a parse-time cost, not a run-time cost.

That said, the actual question was whether using double quoted strings "decreases performance in any meaningful way", to which the answer is a decisive "no". The difference in performance is so incredibly small that it is completely insignificant compared to any real performance concerns. Don't waste your time.

Actual interpolation is a different story, of course. 'foo' will be almost exactly 1 second faster than "#{sleep 1; nil}foo".

Rein Henrichs
  • 15,437
  • 1
  • 45
  • 55
  • 5
    +1 for noting that the cost is at compile time not at run time, so the highly-voted benchmark-based answers above are misleading. – nohat Dec 04 '11 at 20:07
  • "this is a parse-time cost, not a run-time cost." is the key phrase. – the Tin Man Mar 10 '15 at 23:56
9

Thought I'd add a comparison of 1.8.7 and 1.9.2. I ran them a few times. Variance was about +-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.2p0 (2010-08-18 revision 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)
PhilT
  • 4,166
  • 1
  • 36
  • 26
8

Double quotes take twice as many key strikes to type than single quotes. I'm always in a hurry. I use single quotes. :) And yes, I consider that a "performance gain". :)

aqn
  • 2,542
  • 1
  • 16
  • 12
  • Why would double quotes take 2x the key strikes? They are both represented by a single key. Additionally, many IDEs add the closing quote(s) automatically. – Matt Dressel Jul 30 '13 at 18:20
  • 3
    Even if the IDE automatically closes the quote, double quotes still require 100% more key strikes. ;-) – Clint Pachl Aug 24 '13 at 02:32
  • Matt Dressel: double quotes requires twice the number of key strikes because you need to strike the shift key as well. Oh: :) just in case you missed it in my original comment. :) Corded keys require more effort and arguably, more time, to execute. :) – aqn Aug 30 '13 at 15:26
  • 1
    Sometimes I follow this advice out of laziness. But unfortunately in some other languages, it's the opposite (e.g. single quotes need Shift+something while double quotes are a single keystroke). Unfortunate because if two people with different keyboard layouts work on the same project, one of them will have to sacrifice some keystrokes :) – Halil Özgür Mar 28 '16 at 18:41
  • "I'm a man in a hurry" - Unless you press Shift and 2 (or which ever other key it is) one after another you don't save any time at all by using single quotes. – Machisuji Jul 19 '16 at 13:22
3

There is no significant difference in either direction. It would have to be huge for it to matter.

Except for times when you are sure that there is an actual problem with timing, optimize for programmer maintainability.

The costs of machine time are very very small. The costs of programmer time to write code and maintain it is huge.

What good is an optimization to save seconds, even minutes of runtime over thousands of runs if it means that the code is harder to maintain?

Pick with a style and stick with it but do not pick that style based on statistically insignificant milliseconds of runtime.

Andy Lester
  • 91,102
  • 13
  • 100
  • 152
1

I tried the following:

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

And these are the outputs:

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

If I made no mistake, it seems to me that both take approximately the same time, even though single quoted is slightly faster in most cases.

Marcelo Xavier
  • 147
  • 1
  • 7
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)
grilix
  • 5,211
  • 5
  • 33
  • 34
0

There's one you all missed.

HERE doc

try this

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

It gave me

`asign here doc  0.141000   0.000000   0.141000 (  0.140625)`

and

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

so it's certainly better than concat and writing all those puts.

I would like to see Ruby taught more along the lines of a document manipulation language.

After all, don't we really do that in Rails, Sinatra, and running tests?

Douglas G. Allen
  • 2,203
  • 21
  • 20
0

It's certainly possible depending on the implementation, but the scanning portion of the interpreter should only look at each character once. It will need just an additional state (or possible set of states) and transitions to handle #{} blocks.

In a table based scanner thats going to be a single lookup to determine transition, and will be happening for each character anyways.

When the parser gets the scanner output, it's already known that it will have to eval code in the block. So the overhead is only really the memory overhead in the scanner/parser to handle the #{} block, which you pay for either way.

Unless I'm missing something (or misremembering compiler construction details), which is also certainly possible :)

µBio
  • 10,668
  • 6
  • 38
  • 56
0

I modded Tim Snowhite's answer.

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

Results:

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)
Nick
  • 1,174
  • 11
  • 20
0

I too thought that single quoted strings might be quicker to parse for Ruby. It doesn't seem to be the case.

Anyway, I think the above benchmark are measuring the wrong thing, though. It stands to reason that either versions will be parsed into the same internal string representations so to get the answer as to which is quicker to parse, we shouldn't be measuring performance with string variables, but rather Ruby's speed of parsing strings.

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

Repeated runs don't seem to make much difference. It still takes pretty much the same time to parse either version of the string.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142