Skip to content

Instantly share code, notes, and snippets.

@larskanis
Last active February 6, 2020 13:09
Show Gist options
  • Save larskanis/c1ef84c3e058e819786f5fd20bc7c207 to your computer and use it in GitHub Desktop.
Save larskanis/c1ef84c3e058e819786f5fd20bc7c207 to your computer and use it in GitHub Desktop.
string_alloc_test
require "mkmf"
create_makefile("string_alloc_test")
raise "Unable to compile" unless system("make")
require_relative "string_alloc_test.so"
require "benchmark/ips"
include StringAllocTest
def r_alloc
"abcdef"
end
@str = nil
def r_reuse
@str ||= "abcdef"
end
def r_freeze
-"abcdef"
end
Benchmark.ips do |x|
x.time = 0.2
x.warmup = 0.1
puts "ruby #{RUBY_VERSION}"
[:c_alloc, :c_reuse, :r_alloc, :r_reuse, :r_freeze].each do |m|
p [send(m), send(m).object_id, send(m), send(m).object_id]
x.report(m, m.to_s)
end
x.report("new string literal", '"abcdef"')
x.report("reuse string literal", '-"abcdef"')
end
@larskanis
Copy link
Author

larskanis commented Feb 5, 2020

The corresponding C code is here.

Intel 4 core i5-2400:

$ ruby string_alloc_test.rb 
creating Makefile
linking shared-object string_alloc_test.so
ruby 2.7.0
["abcdef", 60, "abcdef", 80]
["abcdef", 100, "abcdef", 100]
["abcdef", 120, "abcdef", 140]
["abcdef", 160, "abcdef", 160]
["abcdef", 180, "abcdef", 180]
Calculating -------------------------------------
             c_alloc     13.259M (± 1.8%) i/s -      2.797M in   0.211012s
             c_reuse     19.190M (± 1.7%) i/s -      4.075M in   0.212425s
             r_alloc     15.208M (± 1.9%) i/s -      3.258M in   0.214293s
             r_reuse     15.964M (± 1.2%) i/s -      3.574M in   0.223916s
            r_freeze     28.667M (± 1.4%) i/s -      5.735M in   0.200111s
  new string literal     59.730M (± 1.2%) i/s -     12.411M in   0.207820s
reuse string literal     49.612M (± 1.3%) i/s -     10.239M in   0.206415s

@larskanis
Copy link
Author

larskanis commented Feb 5, 2020

low power AMD-C50 CPU, Ruby-2.7.0:

$ ruby string_alloc_test.rb 
ruby 2.7.0
Calculating -------------------------------------
             c_alloc      2.156M (± 1.3%) i/s -    439.152k in   0.203710s
             c_reuse      3.093M (± 0.6%) i/s -    649.242k in   0.209913s
             r_alloc      2.206M (± 1.8%) i/s -    491.265k in   0.222723s
             r_reuse      2.542M (± 0.7%) i/s -    526.340k in   0.207047s
            r_freeze      4.038M (± 7.1%) i/s -    823.836k in   0.205290s
  new string literal     13.088M (± 4.3%) i/s -      2.657M in   0.203476s
reuse string literal     10.688M (± 8.1%) i/s -      2.167M in   0.204137s

@larskanis
Copy link
Author

larskanis commented Feb 5, 2020

low power AMD-C50 CPU, Ruby-2.3.7:

$ ruby string_alloc_test.rb 
ruby 2.3.7
["abcdef", 47454878193680, "abcdef", 47454878193500]
["abcdef", 47454878189920, "abcdef", 47454878189920]
["abcdef", 47454878170240, "abcdef", 47454878170020]
["abcdef", 47454878166860, "abcdef", 47454878166860]
["abcdef", 47454878165380, "abcdef", 47454878164260]
Calculating -------------------------------------
             c_alloc      2.053M (± 3.7%) i/s -    434.646k in   0.211978s
             c_reuse      3.342M (± 1.7%) i/s -    705.026k in   0.211020s
             r_alloc      2.770M (± 3.8%) i/s -    554.708k in   0.200517s
             r_reuse      2.564M (± 0.9%) i/s -    559.229k in   0.218156s
            r_freeze      1.374M (± 2.5%) i/s -    309.184k in   0.225096s
  new string literal     10.962M (± 1.5%) i/s -      2.209M in   0.201594s
reuse string literal      1.648M (± 3.3%) i/s -    348.656k in   0.211751s

@larskanis
Copy link
Author

Intel 4 core i5-2400:

$ ruby --jit string_alloc_test.rb 
ruby 2.7.0
Calculating -------------------------------------
             c_alloc     16.085M (± 2.5%) i/s -      3.281M in   0.204119s
             c_reuse     27.802M (± 1.2%) i/s -      5.646M in   0.203111s
             r_alloc     26.708M (± 2.9%) i/s -      5.403M in   0.202481s
             r_reuse     30.087M (± 1.3%) i/s -      6.272M in   0.208512s
            r_freeze    165.940M (± 1.7%) i/s -     33.333M in   0.200935s
  new string literal    302.848M (± 1.9%) i/s -     61.001M in   0.201500s
reuse string literal    275.269M (± 2.1%) i/s -     55.193M in   0.200598s

@ashmaroli
Copy link

Two questions:

  • How is calling :r_alloc different from '"abcdef"' ? (calling overhead?)
  • Is x.report(m, m.to_s) equivalent to x.report(m) { send(m) } ?

I think you should also compare with frozen_string_literal pragma and calling String.new("abcdef") and calling +"abcdef"

@larskanis
Copy link
Author

  • How is calling :r_alloc different from '"abcdef"' ? (calling overhead?)

Exactly, it's with and without the method call.

  • Is x.report(m, m.to_s) equivalent to x.report(m) { send(m) } ?

Similar it's with and without the block invocation, see here.

I think you should also compare with frozen_string_literal pragma and calling String.new("abcdef") and calling +"abcdef"

Feel free to fork and add more variations. For me the comparison between c_alloc and c_reuse was the most interesting part. I extended the C part a bit more to add somewhat more variation to the st_table. It slowed down r_reuse somewhat, but it was still faster than creation of new strings.

@ashmaroli
Copy link

Thank you for the clarifications :)

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