Apr 03, 2020
blog
String concatenation in Ruby
Concatenating strings in Ruby is very easy. We know that Ruby is very developer-friendly, it offers multiple ways to do so. But which one is preferred? Which is more performance efficient? In this article, we'll see different ways to concatenate a string and help you choose the best one.
Ways of concatenating a string in ruby
As stated earlier, there are many ways to concatenate a string. Let's check them one by one by taking an example
Let's say our main string is 'Hello'
and substring to concatenate is ' Ruby!'
So let's define variables and assume we're initiating them before trying each of the methods.
main_string = 'Hello'
substring = ' Ruby!'
1. Using concat method
> main_string.concat(substring)
=> "Hello Ruby!"
# let's print main_string.
> main_string
=> "Hello Ruby!"
2. Using plus i.e. '+' operator
> main_string += substring
=> "Hello Ruby!"
> main_string
=> "Hello Ruby!"
3. Using '<<' operator
> main_string << substring
=> "Hello Ruby!"
> main_string
=> "Hello Ruby!"
4. Using 'insert' method
> main_string.insert(-1, substring)
# -1 is the index which means at the end of string
=> "Hello Ruby!"
> main_string
=> "Hello Ruby!"
5. Ruby string Interpolation
> main_string = "#{main_string}#{substring}"
=> "Hello Ruby!"
NOTE: Kindly note that string Interpolation works when you use double quotes i.e. "" It won't work with single quotes. for e.g. '#{mainstring}#{substring}' won't work, instead it will return "#{mainstring}#{substring}".
6. Array Join method
Though it's not the method on the string directly, it is one of the ways to it.
> main_string = [main_string, substring].join
=> "Hello Ruby!"
Efficient string concatenation in Ruby
So we saw different ways to concatenate strings, you might have decided to use which you liked but hold on let's first do some benchmarking to check performance. We'll do benchmarking in two phases 1. To check time efficiency 2. To check memory efficiency, I'm using Ruby 2.6.3 for benchmarking.
require 'benchmark'
require 'benchmark/ips'
require 'benchmark/memory'
N_NUMBER_OF = 100_000
substring = ' Ruby!'
Benchmark.ips do |x|
x.report("concat") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string.concat(substring) }
end
x.report("plus +") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string += substring }
end
x.report("append <<") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string << substring }
end
x.report("insert ") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string.insert(-1, substring) }
end
x.report("interpolate") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string = "#{main_string}#{substring}" }
end
x.report("Array Join") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string = [main_string, substring].join }
end
x.compare!
end
When N_NUMBER_OF = 100_000
Comparison:
append <<: 72.7 i/s
insert : 66.5 i/s - 1.09x (± 0.00) slower
concat: 34.5 i/s - 2.11x (± 0.00) slower
interpolate: 0.1 i/s - 1240.56x (± 0.00) slower
plus +: 0.1 i/s - 1360.10x (± 0.00) slower
Array Join: 0.1 i/s - 1365.94x (± 0.00) slower
When N_NUMBER_OF = 10000
Comparison:
append <<: 780.8 i/s
insert : 734.1 i/s - same-ish: difference falls within error
concat: 667.2 i/s - 1.17x (± 0.00) slower
interpolate: 5.2 i/s - 149.01x (± 0.00) slower
plus +: 5.2 i/s - 149.44x (± 0.00) slower
Array Join: 5.1 i/s - 151.87x (± 0.00) slower
When N_NUMBER_OF = 1000
Comparison:
append <<: 7411.9 i/s
insert : 6788.0 i/s - 1.09x (± 0.00) slower
concat: 6238.8 i/s - 1.19x (± 0.00) slower
plus +: 929.4 i/s - 7.97x (± 0.00) slower
interpolate: 821.2 i/s - 9.03x (± 0.00) slower
Array Join: 800.2 i/s - 9.26x (± 0.00) slower
The performance of '<<' is fastest amongst all, insert is quite closer to it. If you increase the iterations, you'll find that for 'interpolate' performs better than 'plus +'
N_NUMBER_OF = 10000
substring = ' Ruby!'
Benchmark.memory do |x|
x.report("concat") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string.concat(substring) }
end
x.report("plus +") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string += substring }
end
x.report("append <<") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string << substring }
end
x.report("insert ") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string.insert(-1, substring) }
end
x.report("interpolate") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string = "#{main_string}#{substring}" }
end
x.report("Array Join") do
main_string = 'Hello'
N_NUMBER_OF.times { main_string = [main_string, substring].join }
end
x.compare!
end
Comparison:
concat: 98344 allocated
append <<: 98344 allocated - same
insert : 98344 allocated - same
plus +: 300489986 allocated - 3055.50x more
interpolate: 300500576 allocated - 3055.61x more
Array Join: 300901200 allocated - 3059.68x more
Concat, ‘<<’ and insert doesn’t create new objects and it is straightforward that interpolation and Array's join was creating new objects but '+' is also creating new objects every time, that's why it's not performance efficient.
Conclusion:
Considering the overall benchmark comparison, It's clear that '<<' performs better amongst all, so it should be preferred when performance matters, otherwise it won't make much of the difference which one you use.