Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Improving CVAR Performance in Ruby 3.1

Improving CVAR Performance in Ruby 3.1

Have you ever wondered how class variables (CVARs) in Ruby work? Would you be surprised to learn that their performance becomes increasingly worse as the inheritance chain grows? I’m excited to share that in Ruby 3.1 we fixed the performance of CVARs.

In this talk we'll look at the language design of class variables, learn about how they work, and, deep dive into how we improved their performance in Ruby 3.1 by adding a cache! We'll look at the cache design and real-world benchmarks showing how much faster they are regardless of the size of the inheritance chain.

Eileen M. Uchitelle

November 10, 2021
Tweet

More Decks by Eileen M. Uchitelle

Other Decks in Technology

Transcript

  1. Improving CVAR
    performance in
    Ruby 3.1
    EILEEN M. UCHITELLE | @eileencodes

    View Slide

  2. Thank You RubyConf
    organizers, staff, &
    attendees —

    View Slide

  3. Eileen M. Uchitelle
    @eileencodes

    View Slide

  4. Core Team
    Member
    of Ruby on Rails

    View Slide

  5. f Principal Software
    Engineer
    at GitHub

    View Slide

  6. Improving CVAR
    performance in
    Ruby 3.1

    View Slide

  7. What is a CVAR?

    View Slide

  8. What is a CVAR?
    class MyClass
    @@cvar = 1
    def self.read_cvar
    @@cvar
    end
    end

    View Slide

  9. What is a CVAR?
    class MyClass
    @@cvar = 1
    def self.read_cvar
    @@cvar
    end
    end
    Writer

    View Slide

  10. What is a CVAR?
    class MyClass
    @@cvar = 1
    def self.read_cvar
    @@cvar
    end
    end
    Reader

    View Slide

  11. How does CVAR
    inheritance work?

    View Slide

  12. IVAR inheritance
    class Dog
    attr_reader :name, :owner
    def initialize
    @name = "Arya"
    @owner = "Eileen"
    end
    end
    class Puppy < Dog
    def initialize
    @name = "Sansa"
    end
    end

    View Slide

  13. IVAR inheritance
    > Dog.new.name
    => "Arya"
    > Dog.new.owner
    => "Eileen"
    > Puppy.new.name
    => "Sansa"
    > Puppy.new.owner
    => nil

    View Slide

  14. CVAR inheritance
    class Dog
    @@name = "Arya"
    @@owner = "Eileen"
    def self.name
    @@name
    end
    def self.owner
    @@owner
    end
    end
    class Puppy < Dog
    @@name = "Sansa"
    end

    View Slide

  15. CVAR inheritance
    > Dog.owner
    => "Eileen"
    > Puppy.owner
    => "Eileen"

    View Slide

  16. CVAR inheritance
    > Dog.owner
    => "Eileen"
    > Puppy.owner
    => "Eileen"
    > Dog.name
    => "Sansa"

    View Slide

  17. CVAR inheritance
    > Dog.owner
    => "Eileen"
    > Puppy.owner
    => "Eileen"
    > Dog.name
    => "Sansa"
    > Puppy.name
    => "Sansa"
    🤔

    View Slide

  18. Puppy
    Object
    Dog
    Kernel
    BasicObject

    View Slide

  19. Puppy
    Dog
    Object
    Kernel
    BasicObject
    Stores
    @@name
    @@owner

    View Slide

  20. @@name "Sansa"
    RCLASS_IV_TBL
    Puppy
    Dog

    View Slide

  21. Understanding CVAR
    Overtaken

    View Slide

  22. CVAR Overtaken
    class Dog
    end
    class Puppy < Dog
    @@name = "Sansa"
    def self.name
    @@name
    end
    end

    View Slide

  23. CVAR Overtaken
    class Dog
    end
    class Puppy < Dog
    @@name = "Sansa"
    def self.name
    @@name
    end
    end
    class Dog
    @@name = "Arya"
    end

    View Slide

  24. CVAR Overtaken
    > Puppy.name
    => class variable @@name of Puppy is overtaken
    by Dog

    View Slide

  25. The deeper the
    inheritance chain the
    slower CVAR access is

    View Slide

  26. CVAR Benchmarks
    MODULES = ["A", ..."WWWW"]
    class One
    @@cvar = 1
    def self.cvar
    @@cvar
    end
    eval <<-EOM
    module #{MODULES.first}
    end
    include #{MODULES.first}
    EOM
    end

    View Slide

  27. CVAR Benchmarks
    class Thirty
    @@cvar = 1
    def self.cvar
    @@cvar
    end
    MODULES.take(30).each do |module_name|
    eval <<-EOM
    module #{module_name}
    end
    include #{module_name}
    EOM
    end
    end

    View Slide

  28. CVAR Benchmarks
    class OneHundred
    @@cvar = 1
    def self.cvar
    @@cvar
    end
    MODULES.each do |module_name|
    eval <<-EOM
    module #{module_name}
    end
    include #{module_name}
    EOM
    end
    end

    View Slide

  29. CVAR Benchmarks
    Benchmark.ips do |x|
    x.report "1 module" do
    One.cvar
    end
    x.report "30 modules" do
    Thirty.cvar
    end
    x.report "100 modules" do
    OneHundred.cvar
    end
    x.compare!
    end

    View Slide

  30. CVAR Benchmarks
    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

    View Slide

  31. S
    CVAR Benchmarks
    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 8.5x!

    View Slide

  32. Are CVARs really that
    common?

    View Slide

  33. CVARs in Rails
    module ActiveRecord
    module Core
    def self.configurations=(config)
    @@configurations = ActiveRecord::DatabaseConfigs.new(config)
    end
    self.configurations = {}
    def self.configurations
    @@configurations
    end
    end
    end

    View Slide

  34. CVARs in Rails
    module ActiveRecord
    module Core
    mattr_accessor :logger, instance_writer: false
    mattr_accessor :verbose_query_logs, instance_writer: false, default: false
    mattr_accessor :schema_format, instance_writer: false, default: :ruby
    mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
    mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
    end
    end

    View Slide

  35. CVARs in Rails
    > ActiveRecord::Base.ancestors.count
    => 73
    > ActionController::Base.ancestors.count
    => 71
    > ActiveJob.ancestors.count
    => 29

    View Slide

  36. Building a cache for
    CVARs

    View Slide

  37. View Slide

  38. @@name "Sansa"
    RCLASS_IV_TBL RCLASS_CVC_TBL
    Dog

    View Slide

  39. @@name "Sansa"
    RCLASS_IV_TBL
    @@name
    RCLASS_CVC_TBL
    Dog

    View Slide

  40. @@name "Sansa"
    RCLASS_IV_TBL
    @@name Inline cache
    RCLASS_CVC_TBL
    Dog

    View Slide

  41. @@name "Sansa"
    RCLASS_IV_TBL
    @@name Inline cache
    RCLASS_CVC_TBL
    Dog pointer
    global_cvar_state
    Dog

    View Slide

  42. @@name "Sansa"
    RCLASS_IV_TBL
    @@name Inline cache
    RCLASS_CVC_TBL
    Dog pointer
    global_cvar_state
    Dog
    Puppy

    View Slide

  43. Puppy.name

    View Slide

  44. Puppy.name
    global_cvar_state == GET_GLOBAL_CVAR_STATE()

    View Slide

  45. Puppy.name
    Dog
    global_cvar_state == GET_GLOBAL_CVAR_STATE()

    View Slide

  46. Dog
    Puppy.name
    global_cvar_state == GET_GLOBAL_CVAR_STATE()
    @@name "Sansa"
    RCLASS_IV_TBL

    View Slide

  47. S
    CVAR Cache PR
    github.com/ruby/ruby/pull/4340

    View Slide

  48. Benchmarking
    performance

    View Slide

  49. S
    CVAR Benchmarks
    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 8.5x!

    View Slide

  50. S
    CVAR Benchmarks
    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
    30 modules: 15891406.2 i/s
    No difference!

    View Slide

  51. S
    CVAR Benchmarks
    $ RAILS_ENV=production INTERVAL=100 WARMUP=1 BENCHMARK=10000 ruby bin/bench
    ruby 3.1.0dev (2021-06-04T00:24:57Z master 91c542ad05) [x86_64-darwin19]
    Warming up...
    Warmup: 1 requests
    Benchmark: 10000 requests
    Request per second: 615.1 [#/s] (mean)
    Percentage of the requests served within a certain time (ms)
    50% 1.57
    66% 1.68
    75% 1.74
    80% 1.78
    90% 1.91
    95% 2.06
    98% 2.36
    99% 2.67
    100% 35.15

    View Slide

  52. S
    CVAR Benchmarks
    $ RAILS_ENV=production INTERVAL=100 WARMUP=1 BENCHMARK=10000 ruby bin/bench
    ruby 3.1.0dev (2021-06-04T17:40:20Z add-cache-for.. 37c96af98b) [x86_64-darwin19]
    Warming up...
    Warmup: 1 requests
    Benchmark: 10000 requests
    Request per second: 657.1 [#/s] (mean)
    Percentage of the requests served within a certain time (ms)
    50% 1.46
    66% 1.56
    75% 1.63
    80% 1.68
    90% 1.82
    95% 2.01
    98% 2.28
    99% 2.50
    100% 35.13

    View Slide

  53. What did we
    learn?

    View Slide

  54. 🐞!

    View Slide

  55. S
    Feature Request

    View Slide

  56. Every OSS change
    has tradeoffs

    View Slide

  57. Every OSS change
    has tradeoffs

    View Slide

  58. Tradeoffs
    Increased complexity

    View Slide

  59. Tradeoffs
    Encourages more usage

    View Slide

  60. Tradeoffs
    Increased maintenance
    burden

    View Slide

  61. Every OSS change
    is a negotiation

    View Slide

  62. Negotiation
    CVARs aren't going
    anywhere

    View Slide

  63. Negotiation
    Demonstrate real-world
    improvements

    View Slide

  64. Negotiation
    Improve all applications by
    upgrading

    View Slide

  65. Let's do more of this

    View Slide

  66. Let's do more of this
    By learning C

    View Slide

  67. Let's do more of this
    By reading "Ruby Under a
    Microscope"

    View Slide

  68. Let's do more of this
    By making small changes

    View Slide

  69. Making Ruby better
    benefits everyone

    View Slide

  70. Making Ruby better
    benefits you

    View Slide

  71. Let's go make Ruby
    better, together

    View Slide

  72. Thank You RubyConf!
    EILEEN M. UCHITELLE | @eileencodes

    View Slide