From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-2.7 required=3.0 tests=AWL,BAYES_00, DKIM_ADSP_CUSTOM_MED,FORGED_GMAIL_RCVD,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI,SPF_HELO_NONE,SPF_PASS,UNPARSEABLE_RELAY shortcircuit=no autolearn=no autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id 9B4391F934 for ; Wed, 31 Mar 2021 10:15:26 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 47EB9120A4B; Wed, 31 Mar 2021 19:14:25 +0900 (JST) Received: from xtrwkhkc.outbound-mail.sendgrid.net (xtrwkhkc.outbound-mail.sendgrid.net [167.89.16.28]) by neon.ruby-lang.org (Postfix) with ESMTPS id 1BBFE120A41 for ; Wed, 31 Mar 2021 19:14:21 +0900 (JST) Received: by filterdrecv-p3las1-canary-9b76658bd-9gh9m with SMTP id filterdrecv-p3las1-canary-9b76658bd-9gh9m-19-60644BB1-CA 2021-03-31 10:15:13.87962453 +0000 UTC m=+663966.828831760 Received: from herokuapp.com (unknown) by geopod-ismtpd-4-2 (SG) with ESMTP id T60BQ8UlROaNclDjinWP9w for ; Wed, 31 Mar 2021 10:15:13.628 +0000 (UTC) Date: Wed, 31 Mar 2021 10:15:13 +0000 (UTC) From: eregontp@gmail.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 79168 X-Redmine-Project: ruby-master X-Redmine-Issue-Tracker: Feature X-Redmine-Issue-Id: 17763 X-Redmine-Issue-Author: eileencodes X-Redmine-Sender: Eregon X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-SG-EID: =?us-ascii?Q?KippOI8ZHtTweq7XfQzW93937kJ4QNWwSBuHnaMEcr3e0n8mmq9PEbseER8acp?= =?us-ascii?Q?u2Khvubb9BK6uFiw=2Fscj5cc1GoEC5PLNAItBzsG?= =?us-ascii?Q?+BlpRLUki2YAhDr0lRIfyo9qWTuWfQLEboWyhbW?= =?us-ascii?Q?O0lNAW0SCV=2FlV7=2FvbHC6sf4s1P3JH=2FvFTRzd2io?= =?us-ascii?Q?NtzLFVRpSBqWrUXQHriN4dlDBVlI2c0grV++ZS=2F?= =?us-ascii?Q?aMmBJNFAYX+toYwQ8=3D?= To: ruby-core@ruby-lang.org X-Entity-ID: b/2+PoftWZ6GuOu3b0IycA== X-ML-Name: ruby-core X-Mail-Count: 103125 Subject: [ruby-core:103125] [Ruby master Feature#17763] Implement cache for cvars X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" Issue #17763 has been updated by Eregon (Benoit Daloze). tenderlovemaking (Aaron Patterson) wrote in #note-6: > Also in this case, it makes me feel weird to change the implementation of= Rails when we can just make Ruby perform better. Changing Rails to suit R= uby seems like the tail wagging the dog (obviously not all cases are clear = cut though) I have a different view of this, before this change, class variables were a= lways extremely slow compared to class instance variables (10x in the case = of `ActiveRecord::Base.logger`). So changing it in Rails would fix it for all Ruby versions. The logger seems handled by `mattr_accessor`: https://github.com/rails/rails/blob/d612542336d9a61381311c95a27d801bb409477= 9/activerecord/lib/active_record/core.rb#L20 And `mattr_accessor` is what uses class variables: https://github.com/rails/rails/blob/d612542336d9a61381311c95a27d801bb409477= 9/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb But it seems like it could use an instance variable on the class instead, a= nd use attr_accessor + for instance methods `def foo; self.class.foo` and t= he writer. > but in other places we would have to basically re-implement class variabl= e behavior (and at that point, you may as well use class variables). That might be worth it for performance, i.e., using class methods + class i= nstance variables instead of class variables. Also https://github.com/rails/rails/blob/d612542336d9a61381311c95a27d801bb4= 094779/activesupport/lib/active_support/core_ext/class/attribute.rb#L85 see= ms to have some inheritance and yet already does not use class variables. My personal point of view is class variables are de-facto deprecated syntax= due to their strange and often unexpected semantics, and which about every= Ruby tutorial/book recommends against using. The least usages of class variables we have the better we are IMHO. I once went through removing many class variables in the CRuby repo and it = was fairly easy for most cases IIRC, in part because most of these cases di= d not want any inheritance. --- To clarify, my point is this is good work and now that it's done and I thin= k we should merge it, but it seems worthwhile to look in Rails and other places if class variable= s are really needed. If they can be replaced with class ivars, we could speed those accesses on = all Ruby versions, not just 3.1+. ---------------------------------------- Feature #17763: Implement cache for cvars https://bugs.ruby-lang.org/issues/17763#change-91200 * Author: eileencodes (Eileen Uchitelle) * Status: Open * Priority: Normal ---------------------------------------- # Introduce inline cache for class variable reads @tenderlove and I would like to introduce an inline cache for class variabl= e reads. We've attached a patch that introduces the cache. Class variable r= eads are popular in Rails applications for example, Active Record's `#logge= r`. GitHub PR: https://github.com/ruby/ruby/pull/4340 ## Cache Design This patch introduces a hash table that's stored on the same class as the c= lass variable value. For example: ```ruby class A @@foo =3D 1 end class B < A def self.read_foo @@foo end end ``` The above code stores the value for `@@foo` on the `A` class and stores an = inline cache value on the `A` class as well. The instruction sequences for = the `read_foo` method point at the CVAR inline cache entry stored on class = `A`. The lifecycle of these caches are similar to instance variable inline cache= s. ### Diagram of the cache: ![cvar cache](https://gist.githubusercontent.com/eileencodes/ddd95be978df27= eb76543d352d516449/raw/13e969320159a4e1bff9444694a1ac198e892237/cvar%2520ca= che@2x%2520(6).png) ## Performance Characteristics When class variables are read, Ruby needs to check each class in the inheri= tance tree to ensure that the class variable isn't set on any other classes= in the tree. If the same cvar is set on a class in the inheritance tree th= en a "cvar overtaken" error will be raised. Because of how cvar reads work, the more classes in the inheritance tree th= e more expensive a cvar read is. To demonstrate this here is a benchmark th= at reads a cvar from a class with 1 module, 30 modules, and 100 modules in = the inheritance chain. On Ruby master 100 modules is 8.5x slower than inclu= ding 1 module. With the cache, there is no performance difference between i= ncluding 1 module and including 100 modules. Benchmark script: ```ruby require "benchmark/ips" MODULES =3D ["B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N= ", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "BB", = "CC", "DD", "EE", "FF", "GG", "HH", "II", "JJ", "KK", "LL", "MM", "NN", "OO= ", "PP", "QQ", "RR", "SS", "TT", "UU", "VV", "WW", "XX", "YY", "ZZ", "AAA",= "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH", "III", "JJJ", "KKK", "LLL= ", "MMM", "NNN", "OOO", "PPP", "QQQ", "RRR", "SSS", "TTT", "UUU", "VVV", "W= WW", "XXX", "YYY", "ZZZ", "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "= GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLL", "MMMM", "NNNN", "OOOO", "PPP= P", "QQQQ", "RRRR", "SSSS", "TTTT", "UUUU", "VVVV", "WWWW"] class A @@foo =3D 1 def self.foo @@foo end eval <<-EOM module #{MODULES.first} end include #{MODULES.first} EOM end class Athirty @@foo =3D 1 def self.foo @@foo end MODULES.take(30).each do |module_name| eval <<-EOM module #{module_name} end include #{module_name} EOM end end class Ahundred @@foo =3D 1 def self.foo @@foo end MODULES.each do |module_name| eval <<-EOM module #{module_name} end include #{module_name} EOM end end Benchmark.ips do |x| x.report "1 module" do A.foo end x.report "30 modules" do Athirty.foo end x.report "100 modules" do Ahundred.foo end x.compare! end ``` 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 (=B1 2.1%) i/s - 61.553M in 5.043400s 30 modules 4.354M (=B1 2.7%) i/s - 22.033M in 5.063839s 100 modules 1.434M (=B1 2.9%) i/s - 7.270M in 5.072531s Comparison: 1 module: 12209958.3 i/s 30 modules: 4354217.8 i/s - 2.80x (=B1 0.00) slower 100 modules: 1434447.3 i/s - 8.51x (=B1 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 (=B1 3.8%) i/s - 82.038M in 5.046923s 30 modules 15.891M (=B1 3.9%) i/s - 79.459M in 5.007958s 100 modules 16.087M (=B1 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 er= ror 30 modules: 15891406.2 i/s - same-ish: difference falls within er= ror ``` ### Rails Application Benchmarks We also benchmarked `ActiveRecord::Base.logger` since `logger` is a cvar an= d there are 63 modules in the inheritance chain. This is an example of a re= al-world improvement to Rails applications. Benchmark: ```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: ``` Warming up -------------------------------------- logger 155.251k i/100ms Calculating ------------------------------------- ``` Ruby 3.0 with cvar cache: ``` Warming up -------------------------------------- logger 1.546M i/100ms Calculating ------------------------------------- logger 14.857M (=B1 4.8%) i/s - 74.198M in 5.006202s ``` We also measured database queries in Rails and with the cvar cache they are= about ~9% faster. Benchmark code: ```ruby class BugTest < Minitest::Test = = = def test_association_stuff = = = post =3D Post.create! = = = = = = Benchmark.ips do |x| = = = x.report "query" do = = = Post.first = = = end = = = end = = = end = = = end = = = ``` Ruby 3.0 master / Rails 6.1: ``` Warming up -------------------------------------- query 790.000 i/100ms Calculating ------------------------------------- query 7.601k (=B1 3.8%) i/s - 38.710k in 5.100534s ``` Ruby 3.0 cvar cache / Rails 6.1: ``` Warming up -------------------------------------- query 731.000 i/100ms Calculating ------------------------------------- query 7.089k (=B1 3.3%) i/s - 35.819k in 5.058215s ``` -- = https://bugs.ruby-lang.org/ Unsubscribe: