+ or .concat, what is faster for appending string in Ruby?
Problem:
Yesterday I had to append around ~100-200K small strings (avg. length 7) to form a single large string . I had used + operator and it was very slow. To find a faster alternative I did following benchmarking experiment:
Solution:
TL;DR : use .concat
instead of +
def bm_concat limit
str = ""
limit.times do
str.concat("abcdefg")
end
end
def bm_plus limit
str = ""
limit.times do
str += "abcdefg"
end
end
def diff t1, t2
puts (t2-t1)
end
limit = ARGV[0]
which = ARGV[1]
puts "LIMIT: #{limit} , WHICH: #{which}"
if which == "plus"
t = Time.now
bm_plus limit.to_i
t2 = Time.now
else
t = Time.now
bm_concat limit.to_i
t2 = Time.now
end
diff(t,t2)
On replacing +
with .concat
in my originial code it was 4x faster!
Results of above code for various parameters are :
Written by bitsbyte
Related protips
4 Responses
'+' created new object every time and '.concat' works on the same object.
Thats why 'concat' is faster and that's why you should not use it every time.
Tip: '<<' is an alias for '.concat'.
@korin learned it the hard way ! :)
Expanded this test to also include:
str = "#{str}abcdefg"
Seems to be even slower. ~18 seconds for plus vs. ~23 seconds for the #{str} notation doing 100000 concats. Tried about 10 times each so not super reliable but seems pretty consistent.
Further testing with other concatenations seems to show that str << "abcdefg" is even faster than .concat. But not by much. I would like to hear if anyone else can reproduce these results.
Thanks - very interesting. I was also curious about the results for interpolation so I added to you code and used the Benchmark module to summarize the results.
Ruby Version 2.5.1
========================================
Running for 1000 iterations
----------------------------------------
user system total real
concat: 0.000202 0.000002 0.000204 ( 0.000202)
<<: 0.000340 0.000000 0.000340 ( 0.000342)
plus: 0.001203 0.000719 0.001922 ( 0.001924)
interp: 0.001206 0.000805 0.002011 ( 0.002013)
Running for 10000 iterations
----------------------------------------
user system total real
concat: 0.001593 0.000017 0.001610 ( 0.001610)
<<: 0.000987 0.000001 0.000988 ( 0.000988)
plus: 0.059315 0.048627 0.107942 ( 0.110302)
interp: 0.057374 0.048208 0.105582 ( 0.105909)
Running for 100000 iterations
----------------------------------------
user system total real
concat: 0.011506 0.002310 0.013816 ( 0.013827)
<<: 0.010209 0.000755 0.010964 ( 0.011015)
plus: 7.285013 7.813944 15.098957 ( 15.137750)
interp: 7.290111 7.737633 15.027744 ( 15.293222)
Running for 1000000 iterations
----------------------------------------
user system total real
concat: 0.114216 0.002944 0.117160 ( 0.118727)
<<: 0.103323 0.003833 0.107156 ( 0.110718)
plus: 426.376333 188.171333 614.547666 (619.664617)
interp:429.128582 195.009173 624.137755 (631.839058)
Not surprisingly the plus and interpolation results increase exponentially. concat and << appear to be linear.
And the code
def bm_concat limit
str = ""
limit.times do
str.concat("abcdefg")
end
end
def bm_append limit
str = ""
limit.times do
str << "abcdefg"
end
end
def bm_plus limit
str = ""
limit.times do
str += "abcdefg"
end
end
def bm_interp limit
str = ""
limit.times do
str = "#{str}abcdefg"
end
end
require 'benchmark'
puts "Ruby Version #{RUBY_VERSION}"
puts "="*40
[3,4,5,6].each do |factor|
limit=10**factor
puts "\nRunning for #{limit} iterations"
puts "-"*40
Benchmark.bm do |x|
x.report("concat:") { bm_concat limit }
x.report(" <<:") { bm_append limit }
x.report(" plus:") { bm_plus limit }
x.report("interp:") { bm_interp limit }
end
end
Here's the results from a few other rubies on my system:
Ruby Version 2.3.7
========================================
Running for 1000 iterations
----------------------------------------
user system total real
concat: 0.000000 0.000000 0.000000 ( 0.000153)
<<: 0.000000 0.000000 0.000000 ( 0.000127)
plus: 0.000000 0.010000 0.010000 ( 0.002142)
interp: 0.000000 0.000000 0.000000 ( 0.003452)
Running for 10000 iterations
----------------------------------------
user system total real
concat: 0.000000 0.000000 0.000000 ( 0.001195)
<<: 0.010000 0.000000 0.010000 ( 0.001109)
plus: 0.060000 0.050000 0.110000 ( 0.117216)
interp: 0.080000 0.070000 0.150000 ( 0.146646)
Running for 100000 iterations
----------------------------------------
user system total real
concat: 0.010000 0.000000 0.010000 ( 0.013203)
<<: 0.010000 0.000000 0.010000 ( 0.009131)
plus: 7.350000 7.670000 15.020000 ( 15.086260)
interp: 14.930000 19.590000 34.520000 ( 34.884925)
JRuby Version 9.1.17.0
========================================
Running for 1000 iterations
----------------------------------------
user system total real
concat: 0.020000 0.000000 0.020000 ( 0.002177)
<<: 0.000000 0.000000 0.000000 ( 0.000965)
plus: 0.010000 0.000000 0.010000 ( 0.002923)
interp: 0.010000 0.000000 0.010000 ( 0.002653)
Running for 10000 iterations
----------------------------------------
user system total real
concat: 0.010000 0.000000 0.010000 ( 0.002725)
<<: 0.010000 0.000000 0.010000 ( 0.002532)
plus: 0.280000 0.050000 0.330000 ( 0.118032)
interp: 0.180000 0.000000 0.180000 ( 0.078198)
Running for 100000 iterations
----------------------------------------
user system total real
concat: 0.060000 0.010000 0.070000 ( 0.022531)
<<: 0.050000 0.000000 0.050000 ( 0.018783)
plus: 5.540000 0.140000 5.680000 ( 3.591871)
interp: 7.580000 0.100000 7.680000 ( 4.379781)
JRuby Version 9.2.5.0
========================================
Running for 1000 iterations
----------------------------------------
user system total real
concat: 0.010000 0.000000 0.010000 ( 0.002389)
<<: 0.010000 0.000000 0.010000 ( 0.002574)
plus: 0.020000 0.000000 0.020000 ( 0.009726)
interp: 0.010000 0.000000 0.010000 ( 0.004092)
Running for 10000 iterations
----------------------------------------
user system total real
concat: 0.000000 0.000000 0.000000 ( 0.003194)
<<: 0.010000 0.000000 0.010000 ( 0.003435)
plus: 0.300000 0.040000 0.340000 ( 0.117165)
interp: 0.160000 0.010000 0.170000 ( 0.065159)
Running for 100000 iterations
----------------------------------------
user system total real
concat: 0.060000 0.000000 0.060000 ( 0.024769)
<<: 0.040000 0.000000 0.040000 ( 0.012949)
plus: 5.690000 0.130000 5.820000 ( 3.501719)
interp: 7.380000 0.090000 7.470000 ( 4.272093)
Ruby Version 2.6.0
========================================
Running for 1000 iterations
----------------------------------------
user system total real
concat: 0.000112 0.000004 0.000116 ( 0.000113)
<<: 0.000087 0.000003 0.000090 ( 0.000089)
plus: 0.001099 0.000782 0.001881 ( 0.001882)
interp: 0.001119 0.000784 0.001903 ( 0.001903)
Running for 10000 iterations
----------------------------------------
user system total real
concat: 0.001496 0.000018 0.001514 ( 0.001515)
<<: 0.001005 0.000005 0.001010 ( 0.001010)
plus: 0.062456 0.049374 0.111830 ( 0.112027)
interp: 0.055707 0.043047 0.098754 ( 0.099125)
Running for 100000 iterations
----------------------------------------
user system total real
concat: 0.011245 0.002376 0.013621 ( 0.013622)
<<: 0.009624 0.000204 0.009828 ( 0.009873)
plus: 7.217331 7.229639 14.446970 ( 14.483581)
interp: 7.244183 7.636982 14.881165 ( 15.064573)
surprising - running with --jit in 2.6.0 rc1 has no material effect:
Ruby Version 2.6.0 with --jit
========================================
Running for 1000 iterations
----------------------------------------
user system total real
concat: 0.000890 0.002269 0.066215 ( 0.071825)
<<: 0.000778 0.002200 0.057190 ( 0.063994)
plus: 0.001852 0.003179 0.073733 ( 0.078600)
interp: 0.001985 0.003323 0.074870 ( 0.081532)
Running for 10000 iterations
----------------------------------------
user system total real
concat: 0.002784 0.000990 0.003774 ( 0.003724)
<<: 0.001227 0.000192 0.001419 ( 0.001416)
plus: 0.061307 0.045535 0.106842 ( 0.107475)
interp: 0.055429 0.042227 0.097656 ( 0.097842)
Running for 100000 iterations
----------------------------------------
user system total real
concat: 0.011286 0.002768 0.014054 ( 0.014086)
<<: 0.009409 0.000219 0.009628 ( 0.009694)
plus: 7.252654 7.438926 14.691580 ( 14.733727)
interp: 7.240729 7.408015 14.648744 ( 14.706172)
```