Ruby plus equals (+=) versus append/concatenation shovel (<<)

I was stunned to watch how slow a recent ruby program was. All it was doing was concatenating a bunch of string literals in a big loop. Originally I was using plus equals:


str = ""
1000.times do |i|
  str += "foo bar"
end

On a whim I tried switching to using an array then joining:


str = ""
str_array = []
1000.times do |i|
  str_array << "foo bar"
end
str = str_array.join

Already this was way faster. I wrote up a little benchmarking program to see just how badly "+=" performs compared to "<<". I compare string +=, to the array set up I have above, and just using "<<" on the string:


str = ""
1000.times do |i|
  str << "foo bar"
end

Here's my little test program.


#!/usr/bin/ruby

power = 20

power.times do |p|
  n = 2**p
  str = ""
  start_time = Time.now
  n.times do |i|
    str += "x"
  end
  duration = Time.now - start_time
  #puts "#{n} string appends took: #{duration}s"
  puts "#{n} #{duration}"
end

power.times do |p|
  n = 2**p
  str3 = ""
  start_time = Time.now
  n.times do |i|
    str3 << "x"
  end
  duration = Time.now - start_time
  puts "#{n} #{duration}"
end

power.times do |p|
  n = 2**p
  str2 = ""
  start_time = Time.now
  str_array = []
  n.times do |i|
    str_array << "x"
  end
  str2 = str_array.join
  duration = Time.now - start_time
  puts "#{n} #{duration}"
end

And here are the results:
ruby plus equals vs append

String += is asymptotically worse than <<. Reading through the ruby doc on strings its clear this is because:


str1 += str2

is syntactic sugar for something like


str1 = str1 + str2

whose "=" creates a new string object, hence the big computational cost.

But why?! I can't think of any reason why "+=" shouldn't be syntactic sugar for "<<". Can you? Update:
I get it!
Here's two short snippets that illustrate the difference:


a = "x"
b = a
b += "y"
a

Which results in "x"


a = "x"
b = a
b << "y"
a

Which results in "xy"

It's subjective whether x+=y should mean "append y to x" or always be syntactic sugar for "x = x + y". My vote is for the later, which means I must be content that in Ruby these operators do different things and thus have different speeds.

Tags: ,

3 Responses to “Ruby plus equals (+=) versus append/concatenation shovel (<<)”

  1. I believe something similar happens in Java where, instead of using the += is much better using a stringBuffer. That way you avoid the creation (and garbage collection) of new objects every time.

  2. DSimon says:

    If you want the fast behavior there is the Array#concat method:

    a.concat b

    This appends the contents of array a to the existing array b.

  3. pete says:

    I noticed a slow down in some of my programs when I went to Ruby 2.00 though I’m not much of an expert here because I’m a novice with ruby. But I always enjoy reading code and examples when I can understand them.

Leave a Reply