Skip to content

Instantly share code, notes, and snippets.

@postmodern
Last active July 12, 2021 07:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save postmodern/b664b4a34ea7c1f2598cde04cb5bff5b to your computer and use it in GitHub Desktop.
Save postmodern/b664b4a34ea7c1f2598cde04cb5bff5b to your computer and use it in GitHub Desktop.
Ruby String building benchmarks
#!/usr/bin/env ruby
require 'benchmark'
require 'zlib'
Benchmark.bm(12) do |b|
n = 10_009_000
str1 = 'A'
str2 = 'A'
str3 = 'A'
str4 = 'A'
str5 = 'A'
str6 = 'A'
str7 = 'A'
str8 = 'A'
str9 = 'A'
str10 = 'A'
b.report('interpolated') do
n.times do
"#{str1} #{str2} #{str3} #{str4} #{str5} #{str6} #{str7} #{str8} #{str9} #{str10}"
end
end
b.report('String#<<') do
n.times do
buffer = String.new('', capacity: 19)
buffer << str1 << ' ' << str2 << ' ' << str3 << ' ' << str4 << ' ' << str5 << ' ' << str6 << ' ' << str7 << ' ' << str8 << ' ' << str9 << ' ' << str10
end
end
b.report('String#[]=') do
n.times do
buffer = ' ' * 19
buffer[0,1] = str1
buffer[1,1] = str2
buffer[2,1] = str3
buffer[3,1] = str4
buffer[4,1] = str5
buffer[5,1] = str6
buffer[6,1] = str7
buffer[7,1] = str8
buffer[8,1] = str9
buffer[9,1] = str10
end
end
b.report('Array#join') do
n.times do
[str1, str2, str3, str4, str5, str6, str7, str8, str9, str10].join(' ')
end
end
end
@postmodern
Copy link
Author

                   user     system      total        real
interpolated  10.068467   0.003662  10.072129 ( 10.134907)
String#<<     23.769817   0.003813  23.773630 ( 23.936401)
String#[]=    12.705307   0.001000  12.706307 ( 12.773779)
Array#join    12.967051   0.000001  12.967052 ( 13.038666)

@asterite
Copy link

Interesting!

If you have this code:

"a #{b} c"

and see what bytecode Ruby produces for it:

puts RubyVM::InstructionSequence.compile(%("a \#{b} c")).disassemble

You get this:

== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,10)> (catch: FALSE)
0000 putobject                              "a "                      (   1)[Li]
0002 putself
0003 opt_send_without_block                 <calldata!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0005 dup
0006 checktype                              T_STRING
0008 branchif                               14
0010 dup
0011 opt_send_without_block                 <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
0013 tostring
0014 putobject                              " c"
0016 concatstrings                          3
0018 leave

So it pushes strings to the VM stack, then executes the concatstrings instruction, which is defined here:

https://github.com/ruby/ruby/blob/c2ed5ab08b0f508185b4abd2d28f045616e7c7f5/insns.def#L372-L383

It's still strange that String.new combined with #<< is much slower, maybe << does some checks to see the types, or something like that.

@postmodern
Copy link
Author

and the results of running the benchmarks under benchmark-ips.

Warming up --------------------------------------
        interpolated     1.000  i/100ms
           String#<<     1.000  i/100ms
          String#[]=     1.000  i/100ms
          Array#join     1.000  i/100ms
Calculating -------------------------------------
        interpolated      0.109  (± 0.0%) i/s -      1.000  in   9.209206s
           String#<<      0.045  (± 0.0%) i/s -      1.000  in  22.043854s
          String#[]=      0.076  (± 0.0%) i/s -      1.000  in  13.169221s
          Array#join      0.071  (± 0.0%) i/s -      1.000  in  14.087317s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment