Issue #17763 has been updated by ko1 (Koichi Sasada).


Thank you for the benchmark.

How about that?

>> We also measured database queries in Rails and with the cvar cache they =
are about ~9% faster.
> It seems w/o cache is faster. Could you check it? (opposite results?)





----------------------------------------
Feature #17763: Implement cache for cvars
https://bugs.ruby-lang.org/issues/17763#change-92389

* 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: <mailto:ruby-core-request / ruby-lang.org?subject=3Dunsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>