[ruby-core:114455] [Ruby master Feature#19783] Weak References in the GC
From:
"ko1 (Koichi Sasada) via ruby-core" <ruby-core@...>
Date:
2023-08-23 01:06:35 UTC
List:
ruby-core #114455
Issue #19783 has been updated by ko1 (Koichi Sasada).
* [Bug #19436] is fixed by checking inline method cache data structures.
* So the rest motivation is to support weak reference natively and I don't =
against about it.
* I have two concerns.
1. memory allocation during GC
Allocating memory during GC is not good idea in general (because it can be =
called when memory is not enough). How about to pass the data structure lik=
e that?
```C
struct weak_ref {
VALUE v;
struct weak_ref *prev;
};
rb_gc_mark_weak(struct weak_ref *ref)
{
ref->prev =3D objspace->weaks;
objspace->weaks =3D ref;
}
mark(){
// do mark all
// check weaks
struct weak_ref *wref =3D objspace->weaks;
while (wref) { ... }
}
```
It doesn't need more allocation while GC.
Making such imemo data is also acceptable.
2. huge wrefs
I understand it takes proportional time to marking wref counts (the number =
of `rb_gc_mark_weak()`). I think there is no so much wrefs (especially CME =
doesn't need it) but it can take a time if there are so many wrefs in an ap=
plication.
Could you make such benchmark?
----------------------------------------
Feature #19783: Weak References in the GC
https://bugs.ruby-lang.org/issues/19783#change-104217
* Author: peterzhu2118 (Peter Zhu)
* Status: Open
* Priority: Normal
----------------------------------------
GitHub PR: https://github.com/ruby/ruby/pull/8113
I'm proposing support for weak references in the Ruby garbage collector. Th=
is
feature adds a new function called `void rb_gc_mark_weak(VALUE *ptr)` which
marks `*ptr` as weak, meaning that if no other object strongly marks `*ptr`
(using `rb_gc_mark` or `rb_gc_mark_movable`), then it will be overwritten w=
ith
`*ptr =3D Qundef`.
Weak references are implemented using a buffer in `objspace` that stores all
the `ptr` in the latest marking phase. After marking has finished, we itera=
te
over the buffer and check if the `*ptr` is a dead object. If it is, then we
set `*ptr =3D Qundef`.
Weak references are implemented on the callable method entry (CME) of
callcaches, which fixes issue #19436.
Weak references are also implemented on `ObjectSpace::WeakMap` and
`ObjectSpace::WeakKeyMap`, which have:
- Significantly simpler implementations because we no longer need to have
multiple tables and do not need to define finalizers on the objects.
- Support for compaction because finalizers pin objects and we no longer ne=
ed
to define finalizers on the objects.
- Much faster performance (see [benchmarks](#microbenchmarks)).
## Metrics
This patch also adds two metrics, `GC.latest_gc_info(:weak_references_count=
)`
and `GC.latest_gc_info(:retained_weak_references_count)`. These two metrics
returns information about the number of weak references registered and the
number of weak references retained (references that did not point to a dead
object) in the last GC cycle.
## Benchmark results
### YJIT-bench
We see largely no change in performance or memory usage after this feature.
```
-------------- --------- ---------- --------- ----------- ---------- =
--------- -------------- -----------
bench base (ms) stddev (%) RSS (MiB) branch (ms) stddev (%) =
RSS (MiB) branch 1st itr base/branch
activerecord 72.3 2.2 51.9 72.9 2.2 =
51.9 0.99 0.99
chunky-png 889.2 0.3 43.9 874.5 0.3 =
42.5 1.02 1.02
erubi-rails 21.2 13.5 90.7 21.0 13.3 =
90.9 1.01 1.01
hexapdf 2557.0 0.8 157.1 2559.2 0.7 =
197.1 1.01 1.00
liquid-c 65.2 0.4 34.5 65.4 0.4 =
34.5 0.99 1.00
liquid-compile 62.5 0.4 30.9 62.2 0.4 =
31.0 1.00 1.01
liquid-render 164.6 0.4 33.1 162.6 0.3 =
33.1 1.01 1.01
mail 133.3 0.1 46.4 134.4 0.2 =
46.4 1.03 0.99
psych-load 2066.6 0.2 31.6 2083.6 0.1 =
31.6 0.99 0.99
railsbench 2027.0 0.5 88.8 2019.4 0.5 =
89.0 1.01 1.00
ruby-lsp 65.6 3.0 90.1 65.4 3.1 =
88.5 1.00 1.00
sequel 73.1 1.1 36.6 73.1 1.1 =
36.6 1.00 1.00
-------------- --------- ---------- --------- ----------- ---------- =
--------- -------------- -----------
```
### Microbenchmarks
We can see signficantly improved performance in `ObjectSpace::WeakMap`, with
`ObjectSpace::WeakMap#[]=3D` being nearly 3x faster.
Base:
```
ObjectSpace::WeakMap#[]=3D
1.037M (=B1 0.5%) i/s - 5.262M in 5.072833s
ObjectSpace::WeakMap#[]
12.367M (=B1 0.9%) i/s - 62.479M in 5.052365s
```
Branch:
```
ObjectSpace::WeakMap#[]=3D
3.054M (=B1 0.3%) i/s - 15.448M in 5.058783s
ObjectSpace::WeakMap#[]
15.796M (=B1 4.8%) i/s - 79.245M in 5.028583s
```
Code:
```ruby
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "benchmark-ips"
end
wmap =3D ObjectSpace::WeakMap.new
key =3D Object.new
val =3D Object.new
wmap[key] =3D val
Benchmark.ips do |x|
x.report("ObjectSpace::WeakMap#[]=3D") do |times|
i =3D 0
while i < times
wmap[Object.new] =3D Object.new
i +=3D 1
end
end
x.report("ObjectSpace::WeakMap#[]") do |times|
i =3D 0
while i < times
wmap[key]
wmap[val] # does not exist
i +=3D 1
end
end
end
```
--=20
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- [email protected]
To unsubscribe send an email to [email protected]
ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-c=
ore.ml.ruby-lang.org/