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=-1.8 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_MED,SPF_HELO_NONE,SPF_PASS,UNPARSEABLE_RELAY,URIBL_SBL, URIBL_SBL_A 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 42E121F5AE for ; Fri, 21 May 2021 12:58:59 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 2BDC5120D86; Fri, 21 May 2021 21:57:49 +0900 (JST) Received: from o1678948x4.outbound-mail.sendgrid.net (o1678948x4.outbound-mail.sendgrid.net [167.89.48.4]) by neon.ruby-lang.org (Postfix) with ESMTPS id 46BB0120D21 for ; Fri, 21 May 2021 21:57:47 +0900 (JST) Received: by filterdrecv-77df4fc8dd-slkhl with SMTP id filterdrecv-77df4fc8dd-slkhl-1-60A7AE87-3E 2021-05-21 12:58:47.622655233 +0000 UTC m=+1276263.349405132 Received: from herokuapp.com (unknown) by geopod-ismtpd-4-1 (SG) with ESMTP id y1WBJo04RX2wzy9OtuEg8Q for ; Fri, 21 May 2021 12:58:47.386 +0000 (UTC) Date: Fri, 21 May 2021 12:58:47 +0000 (UTC) From: jean.boussier@gmail.com Message-ID: References: Mime-Version: 1.0 X-Redmine-Project: ruby-master X-Redmine-Issue-Tracker: Feature X-Redmine-Issue-Id: 17763 X-Redmine-Issue-Author: eileencodes X-Redmine-Sender: byroot 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-Redmine-MailingListIntegration-Message-Ids: 79996 X-SG-EID: =?us-ascii?Q?AchqQMoUBMcQgz7gop0XiYUiatGIY7E61JGsTL4FvjdS6IMeQ0ki=2FODWZOj6ZI?= =?us-ascii?Q?aO2T1LoCH=2F=2FeRtcT2lIvsqwNQj7iguTqvcR44Zy?= =?us-ascii?Q?A065gQgS+qvJ9tNUlgKm39tWYgVlXT6RUM3ZH7B?= =?us-ascii?Q?Dn5G8a60HsH0ubB9e+Ie518ke1kPAgCa1LsjeRM?= =?us-ascii?Q?x5E25izIum3Z=2FP3hLK5cBv2WsrvFuD433+mImiT?= =?us-ascii?Q?A4yZ5agbGFiZ1qKqc=3D?= To: ruby-core@ruby-lang.org X-Entity-ID: b/2+PoftWZ6GuOu3b0IycA== X-ML-Name: ruby-core X-Mail-Count: 103949 Subject: [ruby-core:103949] [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 byroot (Jean Boussier). > FWIW, ActiveRecord::Base.logger no longer uses class variables since http= s://github.com/rails/rails/commit/dcc2530af74cf6355a9206bb1d0b084a734fae3e Yes, we've talked about changing it since, it's not really related to this = ticket. IMHO the two are orthogonal, `AR::Base.logger` just happened to be = a good example of the potential performance impact. Also note that it was replaced by `class_attribute`, which is a "Railsism" = and has a non-trivial implementation. Non-rails projects with a similar pro= blems don't really have a solution for this. > Maybe we should really just deprecate class variables since their semanti= cs are confusing = The problem is that in some cases there's not really any alternative, so I'= m all for deprecating them, but IMHO a replacement with better semantic is = needed. Or maybe their behavior could be changed with some kind of switch: ```ruby class MyClass new_class_variable_semantic @@foo =3D 1 end ``` Which would open the door to changing their semantic over the course of a f= ew releases. ---------------------------------------- Feature #17763: Implement cache for cvars https://bugs.ruby-lang.org/issues/17763#change-92080 * 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: