Skip to content
  • eileencodes's avatar
    b91b3bc7
    Add a cache for class variables · b91b3bc7
    eileencodes authored
    
    
    Redo of 34a2acdac788602c14bf05fb616215187badd504 and
    931138b00696419945dc03e10f033b1f53cd50f3 which were reverted.
    
    GitHub PR #4340.
    
    This change implements a cache for class variables. Previously there was
    no cache for cvars. Cvar access is slow due to needing to travel all the
    way up th ancestor tree before returning the cvar value. The deeper the
    ancestor tree the slower cvar access will be.
    
    The benefits of the cache are more visible with a higher number of
    included modules due to the way Ruby looks up class variables. The
    benchmark here includes 26 modules and shows with the cache, this branch
    is 6.5x faster when accessing class variables.
    
    ```
    compare-ruby: ruby 3.1.0dev (2021-03-15T06:22:34Z master 9e5105ca) [x86_64-darwin19]
    built-ruby: ruby 3.1.0dev (2021-03-15T12:12:44Z add-cache-for-clas.. c6be009) [x86_64-darwin19]
    
    |         |compare-ruby|built-ruby|
    |:--------|-----------:|---------:|
    |vm_cvar  |      5.681M|   36.980M|
    |         |           -|     6.51x|
    ```
    
    Benchmark.ips calling `ActiveRecord::Base.logger` from within a Rails
    application. ActiveRecord::Base.logger has 71 ancestors. The more
    ancestors a tree has, the more clear the speed increase. IE if Base had
    only one ancestor we'd see no improvement. This benchmark is run on a
    vanilla Rails application.
    
    Benchmark code:
    
    ```ruby
    require "benchmark/ips"
    require_relative "config/environment"
    
    Benchmark.ips do |x|
      x.report "logger" do
        ActiveRecord::Base.logger
      end
    end
    ```
    
    Ruby 3.0 master / Rails 6.1:
    
    ```
    Warming up --------------------------------------
                  logger   155.251k i/100ms
    Calculating -------------------------------------
    ```
    
    Ruby 3.0 with cvar cache /  Rails 6.1:
    
    ```
    Warming up --------------------------------------
                  logger     1.546M i/100ms
    Calculating -------------------------------------
                  logger     14.857M (± 4.8%) i/s -     74.198M in   5.006202s
    ```
    
    Lastly we ran a benchmark to demonstate the difference between master
    and our cache when the number of modules increases. This benchmark
    measures 1 ancestor, 30 ancestors, and 100 ancestors.
    
    Ruby 3.0 master:
    
    ```
    Warming up --------------------------------------
                1 module     1.231M i/100ms
              30 modules   432.020k i/100ms
             100 modules   145.399k i/100ms
    Calculating -------------------------------------
                1 module     12.210M (± 2.1%) i/s -     61.553M in   5.043400s
              30 modules      4.354M (± 2.7%) i/s -     22.033M in   5.063839s
             100 modules      1.434M (± 2.9%) i/s -      7.270M in   5.072531s
    
    Comparison:
                1 module: 12209958.3 i/s
              30 modules:  4354217.8 i/s - 2.80x  (± 0.00) slower
             100 modules:  1434447.3 i/s - 8.51x  (± 0.00) slower
    ```
    
    Ruby 3.0 with cvar cache:
    
    ```
    Warming up --------------------------------------
                1 module     1.641M i/100ms
              30 modules     1.655M i/100ms
             100 modules     1.620M i/100ms
    Calculating -------------------------------------
                1 module     16.279M (± 3.8%) i/s -     82.038M in   5.046923s
              30 modules     15.891M (± 3.9%) i/s -     79.459M in   5.007958s
             100 modules     16.087M (± 3.6%) i/s -     81.005M in   5.041931s
    
    Comparison:
                1 module: 16279458.0 i/s
             100 modules: 16087484.6 i/s - same-ish: difference falls within error
              30 modules: 15891406.2 i/s - same-ish: difference falls within error
    ```
    
    Co-authored-by: default avatarAaron Patterson <tenderlove@ruby-lang.org>
    b91b3bc7
    Add a cache for class variables
    eileencodes authored
    
    
    Redo of 34a2acdac788602c14bf05fb616215187badd504 and
    931138b00696419945dc03e10f033b1f53cd50f3 which were reverted.
    
    GitHub PR #4340.
    
    This change implements a cache for class variables. Previously there was
    no cache for cvars. Cvar access is slow due to needing to travel all the
    way up th ancestor tree before returning the cvar value. The deeper the
    ancestor tree the slower cvar access will be.
    
    The benefits of the cache are more visible with a higher number of
    included modules due to the way Ruby looks up class variables. The
    benchmark here includes 26 modules and shows with the cache, this branch
    is 6.5x faster when accessing class variables.
    
    ```
    compare-ruby: ruby 3.1.0dev (2021-03-15T06:22:34Z master 9e5105ca) [x86_64-darwin19]
    built-ruby: ruby 3.1.0dev (2021-03-15T12:12:44Z add-cache-for-clas.. c6be009) [x86_64-darwin19]
    
    |         |compare-ruby|built-ruby|
    |:--------|-----------:|---------:|
    |vm_cvar  |      5.681M|   36.980M|
    |         |           -|     6.51x|
    ```
    
    Benchmark.ips calling `ActiveRecord::Base.logger` from within a Rails
    application. ActiveRecord::Base.logger has 71 ancestors. The more
    ancestors a tree has, the more clear the speed increase. IE if Base had
    only one ancestor we'd see no improvement. This benchmark is run on a
    vanilla Rails application.
    
    Benchmark code:
    
    ```ruby
    require "benchmark/ips"
    require_relative "config/environment"
    
    Benchmark.ips do |x|
      x.report "logger" do
        ActiveRecord::Base.logger
      end
    end
    ```
    
    Ruby 3.0 master / Rails 6.1:
    
    ```
    Warming up --------------------------------------
                  logger   155.251k i/100ms
    Calculating -------------------------------------
    ```
    
    Ruby 3.0 with cvar cache /  Rails 6.1:
    
    ```
    Warming up --------------------------------------
                  logger     1.546M i/100ms
    Calculating -------------------------------------
                  logger     14.857M (± 4.8%) i/s -     74.198M in   5.006202s
    ```
    
    Lastly we ran a benchmark to demonstate the difference between master
    and our cache when the number of modules increases. This benchmark
    measures 1 ancestor, 30 ancestors, and 100 ancestors.
    
    Ruby 3.0 master:
    
    ```
    Warming up --------------------------------------
                1 module     1.231M i/100ms
              30 modules   432.020k i/100ms
             100 modules   145.399k i/100ms
    Calculating -------------------------------------
                1 module     12.210M (± 2.1%) i/s -     61.553M in   5.043400s
              30 modules      4.354M (± 2.7%) i/s -     22.033M in   5.063839s
             100 modules      1.434M (± 2.9%) i/s -      7.270M in   5.072531s
    
    Comparison:
                1 module: 12209958.3 i/s
              30 modules:  4354217.8 i/s - 2.80x  (± 0.00) slower
             100 modules:  1434447.3 i/s - 8.51x  (± 0.00) slower
    ```
    
    Ruby 3.0 with cvar cache:
    
    ```
    Warming up --------------------------------------
                1 module     1.641M i/100ms
              30 modules     1.655M i/100ms
             100 modules     1.620M i/100ms
    Calculating -------------------------------------
                1 module     16.279M (± 3.8%) i/s -     82.038M in   5.046923s
              30 modules     15.891M (± 3.9%) i/s -     79.459M in   5.007958s
             100 modules     16.087M (± 3.6%) i/s -     81.005M in   5.041931s
    
    Comparison:
                1 module: 16279458.0 i/s
             100 modules: 16087484.6 i/s - same-ish: difference falls within error
              30 modules: 15891406.2 i/s - same-ish: difference falls within error
    ```
    
    Co-authored-by: default avatarAaron Patterson <tenderlove@ruby-lang.org>
Loading