In another blog we compared string concatenation and interpolation in Ruby. In that blog, I claimed that “generally, you’ll want to stick with interpolation, as it’s quicker”. However, I didn’t really elaborate or provide any benchmarks. I noticed the post was linked in a reddit comment about this very topic, and I figured I needed to provide an update. So let’s do some benchmarking!

Skip to the results!

Benchmarking Setup

We can use benchmark from the Ruby standard library to easily accomplish our goal of comparing the performance of string concatenation and string interpolation in various scenarios.

Here’s the code for our test:

require 'benchmark'

e = "everybody"
w = "wang"
c = "chung"
t = "tonight"

## Test 1
concat = Benchmark.measure {
  5_000_000.times do
    string = "everybody" + " " + "wang" + " "  + "chung" + " " + "tonight"
  end
}

interp = Benchmark.measure {
  5_000_000.times do
    string = "#{"everybody"} #{"wang"} #{"chung"} #{"tonight"}"
  end
}


## Test 2
concat_var = Benchmark.measure {
  5_000_000.times do
    string = e + " " + w + " "  + c + " " + t
  end
}

interp_var = Benchmark.measure {
  5_000_000.times do
    string = "#{e} #{w} #{c} #{t}"
  end
}

## Test 3
concat_2 = Benchmark.measure {
  5_000_000.times do
    string = e + w
  end
}

interp_2 = Benchmark.measure {
  5_000_000.times do
    string = "#{e}wang"
  end
}

puts "Test #1:"
puts "concat: " + concat.to_s
puts "interp: #{interp}"
puts ""
puts "Test #2:"
puts "concat_var: " + concat_var.to_s
puts "interp_var: #{interp_var}"
puts ""
puts "Test #3:"
puts "concat_2: " + concat_2.to_s
puts "interp_2: #{interp_2}"

Test Rundown

So, in the above, we have three tests for both methods.

Test #1

In the standard case, we build the string “everybody wang chung tonight” with both methods. We use literal strings everywhere, not variables. These tests are in the code under concat and interp.

Test #2

Next, I wanted to see if there was any difference if the word strings for our phrase above were stored in variables. You can find these in the code as concat_var and interp_var.

Test #3

Lastly, I wanted to test a more modest case. I decided to concatenate two string variables, and interpolate one string variable into another literal string. You can find these under concat_2 and interp_2.

Hypothesis

My understanding is that concatenation takes longer because it makes an intermediate string for each addend. It would follow that if I were only concatenating a few strings, this affect would be less pronounced. It could be that concatenation beats interpolation in this case. Anyways, let’s run the code and find out.

Results

Note: The columns indicate (from left to right) in seconds:

  • User CPU Time
  • System CPU Time
  • User + Kernel CPU Time
  • Real elapsed Time
$ ruby ./benchmark_strings.rb | column -t

Test         #1:
concat:      3.112053  0.001308  3.113361  (  3.126933)
interp:      0.267799  0.000005  0.267804  (  0.269232)
Test         #2:
concat_var:  2.926580  0.000046  2.926626  (  2.936785)
interp_var:  1.801875  0.000000  1.801875  (  1.802651)
Test         #3:
concat_2:    0.442123  0.000000  0.442123  (  0.442274)
interp_2:    0.480152  0.000000  0.480152  (  0.482184)



$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]

So, what does this tell us?

  • Well, interpolation clearly wins with the longer string cases.
  • Variables vs. literals has a much more pronounced effect on interpolation than concatenation, with interpolation increasing in runtime by almost a full order of magnitude.
  • The 2-string cases are really really close for both. I ran this a few times and they were always close. It seemed like interpolation was more often lower, so I decided to increase the loop count and run it again with 50,000,000 iterations instead of 5,000,000:
$ ruby ./benchmark_strings2.rb | column -t

concat_2:            4.485134  0.000782  4.485916  (  4.487549)
interp_2:  	     4.760736  0.000000  4.760736  (  4.766114)

Well, what do you know?! I’m glad I ran it again, because this convinces me that concatenating 2 strings is faster than interpolating them. However, the difference is so negligible that I will probably just pick whatever I think is more readable.

Summary

So, in summary:

  • Interpolation is more efficient if you’re working with lots of strings.
  • If you just have a few strings, concatenation might be slightly faster.

Other things to consider:

  • Interpolation can be easier to extend/modify in the future.
  • Using interpolation for a literal and a variable is cool, but interpolating 2 variables looks silly if you aren’t including any literal strings:
"other ${thing} to"      # good
"other " + thing + " to" # tedious
string1 + string2        # good
"#{string1}#{string2}"   # tedious, unless you know you'll add something later

May 2021 Update - Ruby 3.0.1

So, it’s been a while since I’ve run this script. With Ruby 3 out for a while now, I wanted to give it a whirl. I reran both benchmark scripts above using Ruby 3.0.1:

$ ruby ./benchmark_strings.rb | column -t
Concatenation:                     4.235503  0.000265  4.235768  (  4.245937)
Var-Concatenation:                 3.808327  0.000005  3.808332  (  3.813126)
2-string-Concatenation:            0.664020  0.000001  0.664021  (  0.664251)
Interpolation:                     0.352835  0.000000  0.352835  (  0.352962)
Var-Interpolation:                 2.196492  0.000001  2.196493  (  2.205831)
2-string-(one-var)-Interpolation:  0.643647  0.000000  0.643647  (  0.647448)



$ ruby -v
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-linux]



$ ruby ./benchmark_strings2.rb | column -t
2-string-Concatenation:            6.351739  0.000010  6.351749  (  6.353692)
2-string-(one-var)-Interpolation:  6.225715  0.008004  6.233719  (  6.243056)

These results are quite a bit different than 2.6.5’s. I re-ran them on 2.6.5 again just to make sure nothing had changed on my machine, and I got the same numbers as posted in this blog originally.

I realize this isn’t real-world code, but I was expecting better performance from 3. I’ll be sure to check in as newer releases of 3 come out.

September 2023 Update - Ruby 3.2.2

Hey there, we’re back for another update with Ruby 3.2.2:

$ ruby ./benchmark_strings.rb | column -t
Concatenation:                     4.407986  0.006063  4.414049  (  4.417258)
Var-Concatenation:                 3.708582  0.005998  3.714580  (  3.717277)
2-string-Concatenation:            0.625987  0.000002  0.625989  (  0.626477)
Interpolation:                     0.383833  0.000000  0.383833  (  0.384158)
Var-Interpolation:                 1.614560  0.001998  1.616558  (  1.617659)
2-string-(one-var)-Interpolation:  0.584162  0.000998  0.585160  (  0.585618)



$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]



$ ruby ./benchmark_strings2.rb | column -t
2-string-Concatenation:            6.216848  0.011332  6.228180  (  6.232978)
2-string-(one-var)-Interpolation:  5.819339  0.005997  5.825336  (  5.829687)

The most notable thing I see here is that one-variable string interpolation shaved off almost half a second since 3.0.1. This is nice because it’s much easier to extend strings using interpolation than concatenation when you need to add more variables later, but with a difference this small, and a very small sample size as far as testing, I wouldn’t get too hung up on this.

See you again soon.