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.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_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 4D2421F5AE for ; Fri, 4 Jun 2021 17:46:45 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 9387B120B2B; Sat, 5 Jun 2021 02:45:34 +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 A9BC0120B18 for ; Sat, 5 Jun 2021 02:45:31 +0900 (JST) Received: by filterdrecv-79b856d5bc-ft42g with SMTP id filterdrecv-79b856d5bc-ft42g-1-60BA66F8-12 2021-06-04 17:46:32.185000567 +0000 UTC m=+75395.330081254 Received: from herokuapp.com (unknown) by ismtpd0145p1iad2.sendgrid.net (SG) with ESMTP id jEOrALcfTxa5rqbW0fGPkw for ; Fri, 04 Jun 2021 17:46:32.012 +0000 (UTC) Date: Fri, 04 Jun 2021 17:46:32 +0000 (UTC) From: eileencodes@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: eileencodes 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: 80249 X-SG-EID: =?us-ascii?Q?OsAAnaoahvGE1Smyl5pGl+3Y0HQT8Sh2mm6KgSlol8LZCl7ir0Z3muyJlUQZNj?= =?us-ascii?Q?CFWqbaiu=2F8h8h95SB3mKtfhhxp9stgxPzcyfiCP?= =?us-ascii?Q?kS2kr2syoyrqOQMCKCW1gLaTlj6Y=2Fxe3pVT2+zK?= =?us-ascii?Q?jWOjaweO2X7b50Md3XnG=2FR86mq5pvuaX=2Fr9ds1c?= =?us-ascii?Q?3m1ISF4LEaXKiWlA3vc5LKBP7ASdqejDiSw=3D=3D?= To: ruby-core@ruby-lang.org X-Entity-ID: b/2+PoftWZ6GuOu3b0IycA== X-ML-Name: ruby-core X-Mail-Count: 104167 Subject: [ruby-core:104167] [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 eileencodes (Eileen Uchitelle). I ran benchmarks using railsbench and the branch with the CVAR cache is a l= ot faster, 657 requests per second over the 615 requests per second on mast= er. Master: ``` $ RAILS_ENV=3Dproduction INTERVAL=3D100 WARMUP=3D1 BENCHMARK=3D10000 ruby b= in/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 ``` CVAR Branch ``` $ RAILS_ENV=3Dproduction INTERVAL=3D100 WARMUP=3D1 BENCHMARK=3D10000 ruby b= in/bench ruby 3.1.0dev (2021-06-04T17:40:20Z add-cache-for-clas.. 37c96af98b) [x86_6= 4-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 `` ---------------------------------------- Feature #17763: Implement cache for cvars https://bugs.ruby-lang.org/issues/17763#change-92341 * 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: