Make setting and accessing class ivars lock-free

Now that class fields have been deletated to a T_IMEMO/class_fields
when we're in multi-ractor mode, we can read and write class instance
variable in an atomic way using Read-Copy-Update (RCU).

Note when in multi-ractor mode, we always use RCU. In theory
we don't need to, instead if we ensured the field is written
before the shape is updated it would be safe.

Benchmark:

```ruby
Warning[:experimental] = false

class Foo
  @foo = 1
  @bar = 2
  @baz = 3
  @egg = 4
  @spam = 5

  class << self
    attr_reader :foo, :bar, :baz, :egg, :spam
  end
end

ractors = 8.times.map do
  Ractor.new do
    1_000_000.times do
      Foo.bar + Foo.baz * Foo.egg - Foo.spam
    end
  end
end

if Ractor.method_defined?(:value)
  ractors.each(&:value)
else
  ractors.each(&:take)
end
```

This branch vs Ruby 3.4:

```bash
$ hyperfine -w 1 'ruby --disable-all ../test.rb' './miniruby ../test.rb'

Benchmark 1: ruby --disable-all ../test.rb
  Time (mean ± σ):      3.162 s ±  0.071 s    [User: 2.783 s, System: 10.809 s]
  Range (min … max):    3.093 s …  3.337 s    10 runs

Benchmark 2: ./miniruby ../test.rb
  Time (mean ± σ):     208.7 ms ±   4.6 ms    [User: 889.7 ms, System: 6.9 ms]
  Range (min … max):   202.8 ms … 222.0 ms    14 runs

Summary
  ./miniruby ../test.rb ran
   15.15 ± 0.47 times faster than ruby --disable-all ../test.rb
```
This commit is contained in:
Jean Boussier 2025-06-12 10:06:03 +02:00
parent 8b5ac5abf2
commit a74c385208
Notes: git 2025-06-12 12:55:26 +00:00
4 changed files with 92 additions and 83 deletions

View file

@ -531,13 +531,6 @@ RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(VALUE obj)
return ext->fields_obj;
}
static inline void
RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
RB_OBJ_WRITE(obj, &ext->fields_obj, fields_obj);
}
static inline VALUE
RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj)
{
@ -545,6 +538,16 @@ RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj)
return RCLASSEXT_FIELDS_OBJ(RCLASS_EXT_WRITABLE(obj));
}
static inline void
RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
VALUE old_fields_obj = ext->fields_obj;
RUBY_ATOMIC_VALUE_SET(ext->fields_obj, fields_obj);
RB_OBJ_WRITTEN(obj, old_fields_obj, fields_obj);
}
static inline void
RCLASS_WRITABLE_SET_FIELDS_OBJ(VALUE obj, VALUE fields_obj)
{