How to quickly join two strings in Ruby
Starting with short strings:
z = 'z''a ' << z # => "a z"'a '.concat(z) # => "a z"'a ' + z # => "a z""a #{z}" # => "a z"require 'fruity'compare do append { 'a ' << z} concat { 'a '.concat(z)} plus { 'a ' + z} interpolate { "a #{z}" }end# >> Running each test 65536 times. Test will take about 2 seconds.# >> interpolate is similar to append# >> append is similar to plus# >> plus is faster than concat by 2x ± 0.1
Increasing the "left" string to 11 characters:
require 'fruity'compare do append { 'abcdefghij ' << z} concat { 'abcdefghij '.concat(z)} plus { 'abcdefghij ' + z} interpolate { "abcdefghij #{z}" }end# >> Running each test 65536 times. Test will take about 2 seconds.# >> interpolate is similar to append# >> append is similar to plus# >> plus is faster than concat by 2x ± 1.0
51 characters:
compare do append { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' << z} concat { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij '.concat(z)} plus { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' + z} interpolate { "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij #{z}" }end# >> Running each test 32768 times. Test will take about 2 seconds.# >> plus is faster than append by 2x ± 1.0# >> append is similar to interpolate# >> interpolate is similar to concat
101:
compare do append { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' << z} concat { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij '.concat(z)} plus { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' + z} interpolate { "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij #{z}" }end# >> Running each test 32768 times. Test will take about 2 seconds.# >> plus is faster than interpolate by 2x ± 0.1# >> interpolate is similar to append# >> append is similar to concat
501:
compare do append { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' << z} concat { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij '.concat(z)} plus { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' + z} interpolate { "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij #{z}" }end# >> Running each test 16384 times. Test will take about 1 second.# >> plus is faster than append by 2x ± 0.1# >> append is similar to interpolate# >> interpolate is similar to concat
Once the strings got past 50 characters +
consistently outperformed the others.
In the comments there are mention of some of these mutating the string on the left. Here's what would happen if it was a variable on the left, not a literal string:
a = 'a'z = 'z'a << z # => "az"a # => "az"a = 'a'a.concat(z) # => "az"a # => "az"
compared to:
a + z # => "az"a # => "a""#{a} #{z}" # => "a z"a # => "a"
Note: The initial version of answer had a bad test using:
"a #{'z'}"
The problem with that is Ruby is smart enough to recognize that 'z'
is another literal and converts the string into:
"a z"
with the end result that the test would be unfairly faster than the others.
It's been a while and Ruby's smarter and faster. I added a couple additional tests:
puts "Running Ruby v%s" % RUBY_VERSIONrequire 'fruity'z_ = 'z'compare do append { 'abcdefghij ' << 'z' } concat { 'abcdefghij '.concat('z') } plus { 'abcdefghij ' + 'z' } interpolate1 { "abcdefghij #{'z'}" } interpolate2 { "abcdefghij #{z_}" } adjacent { 'abcdefghij' ' z' }end# >> Running Ruby v2.7.0# >> Running each test 65536 times. Test will take about 3 seconds.# >> adjacent is similar to interpolate1# >> interpolate1 is faster than interpolate2 by 2x ± 1.0# >> interpolate2 is similar to append# >> append is similar to concat# >> concat is similar to plus
interpolate1
and adjacent
are basically the same as far as the interpreter is concerned and will be concatenated prior to running. interpolate2
forces Ruby to do it at run-time so it's a little slower.