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.

C44e1f7e22c3f23cff7bc130871047ef?s=128

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
  2. Thank You RubyConf organizers, staff, & attendees —

  3. Eileen M. Uchitelle @eileencodes

  4. Core Team Member of Ruby on Rails

  5. f Principal Software Engineer at GitHub

  6. Improving CVAR performance in Ruby 3.1

  7. What is a CVAR?

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

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

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

    self.read_cvar @@cvar end end Reader
  11. How does CVAR inheritance work?

  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
  13. IVAR inheritance > Dog.new.name => "Arya" > Dog.new.owner => "Eileen"

    > Puppy.new.name => "Sansa" > Puppy.new.owner => nil
  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
  15. CVAR inheritance > Dog.owner => "Eileen" > Puppy.owner => "Eileen"

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

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

    > Dog.name => "Sansa" > Puppy.name => "Sansa" 🤔
  18. Puppy Object Dog Kernel BasicObject

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

  20. @@name "Sansa" RCLASS_IV_TBL Puppy Dog

  21. Understanding CVAR Overtaken

  22. CVAR Overtaken class Dog end class Puppy < Dog @@name

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

    = "Sansa" def self.name @@name end end class Dog @@name = "Arya" end
  24. CVAR Overtaken > Puppy.name => class variable @@name of Puppy

    is overtaken by Dog
  25. The deeper the inheritance chain the slower CVAR access is

  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
  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
  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
  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
  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
  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!
  32. Are CVARs really that common?

  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
  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
  35. CVARs in Rails > ActiveRecord::Base.ancestors.count => 73 > ActionController::Base.ancestors.count =>

    71 > ActiveJob.ancestors.count => 29
  36. Building a cache for CVARs

  37. None
  38. @@name "Sansa" RCLASS_IV_TBL RCLASS_CVC_TBL Dog

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

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

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

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

    Dog Puppy
  43. Puppy.name

  44. Puppy.name global_cvar_state == GET_GLOBAL_CVAR_STATE()

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

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

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

  48. Benchmarking performance

  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!
  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!
  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
  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
  53. What did we learn?

  54. 🐞!

  55. S Feature Request

  56. Every OSS change has tradeoffs

  57. Every OSS change has tradeoffs

  58. Tradeoffs Increased complexity

  59. Tradeoffs Encourages more usage

  60. Tradeoffs Increased maintenance burden

  61. Every OSS change is a negotiation

  62. Negotiation CVARs aren't going anywhere

  63. Negotiation Demonstrate real-world improvements

  64. Negotiation Improve all applications by upgrading

  65. Let's do more of this

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

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

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

  69. Making Ruby better benefits everyone

  70. Making Ruby better benefits you

  71. Let's go make Ruby better, together

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