mirror of
https://github.com/ruby/ruby.git
synced 2026-11-08 01:20:26 +08:00
Read the object_id of a shareable T_DATA without the VM lock
object_id took the VM lock for every shareable object in multi-Ractor mode, even
when the object already had an id.
Kernel#hash reaches object_id through rb_obj_hash, so a shareable T_DATA such as
an Encoding, a Ractor or a Ractor::Port used as a Hash key serialised every
lookup across Ractors.
In this commit we make generic_field_set copy the fields of a shareable object
in multi-Ractor mode, and publish the new fields_obj of a T_DATA with an atomic
write.
```ruby
t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
ractors = 4.times.map do
Ractor.new do
h = { Encoding::UTF_8 => 1 }
1_000_000.times { h[Encoding::UTF_8] }
end
end
ractors.each(&:join)
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t
puts "#{elapsed.round(2)}s"
```
I ran this 3 times before this patch and after and saw
before: 0.58s 0.61s 0.64s
after: 0.05s 0.05s 0.05s
This commit is contained in:
parent
1f44a8642a
commit
2a8f329f59
Notes:
git
2026-11-06 20:27:13 +00:00
33
gc.c
33
gc.c
@ -2402,16 +2402,16 @@ class_object_id(VALUE klass)
|
||||
return id;
|
||||
}
|
||||
|
||||
static inline shape_id_t
|
||||
object_id_field_shape(shape_id_t shape_id)
|
||||
{
|
||||
return rb_shape_complex_p(shape_id) ? ROOT_COMPLEX_WITH_OBJ_ID : rb_shape_object_id(shape_id);
|
||||
}
|
||||
|
||||
static inline VALUE
|
||||
object_id_get(VALUE obj, shape_id_t shape_id)
|
||||
{
|
||||
VALUE id;
|
||||
if (rb_shape_complex_p(shape_id)) {
|
||||
id = rb_obj_field_get(obj, ROOT_COMPLEX_WITH_OBJ_ID);
|
||||
}
|
||||
else {
|
||||
id = rb_obj_field_get(obj, rb_shape_object_id(shape_id));
|
||||
}
|
||||
VALUE id = rb_obj_field_get(obj, object_id_field_shape(shape_id));
|
||||
|
||||
#if RUBY_DEBUG
|
||||
if (!(FIXNUM_P(id) || RB_TYPE_P(id, T_BIGNUM))) {
|
||||
@ -2443,6 +2443,20 @@ object_id0(VALUE obj)
|
||||
return id;
|
||||
}
|
||||
|
||||
// generic_field_set never mutates the published fields_obj of a shareable object,
|
||||
// and the fields_obj carries its owner's shape, so both are read from the one snapshot.
|
||||
static VALUE
|
||||
shareable_data_object_id(VALUE obj)
|
||||
{
|
||||
VALUE fields_obj = RUBY_ATOMIC_VALUE_LOAD(RTYPEDDATA(obj)->fields_obj);
|
||||
if (!fields_obj) return Qfalse;
|
||||
|
||||
shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj);
|
||||
if (!rb_shape_has_object_id(shape_id)) return Qfalse;
|
||||
|
||||
return rb_imemo_fields_field_get(fields_obj, object_id_field_shape(shape_id));
|
||||
}
|
||||
|
||||
static VALUE
|
||||
object_id(VALUE obj)
|
||||
{
|
||||
@ -2461,6 +2475,11 @@ object_id(VALUE obj)
|
||||
}
|
||||
|
||||
if (UNLIKELY(rb_gc_multi_ractor_p() && rb_ractor_shareable_p(obj))) {
|
||||
if (BUILTIN_TYPE(obj) == T_DATA) {
|
||||
VALUE existing_id = shareable_data_object_id(obj);
|
||||
if (existing_id) return existing_id;
|
||||
}
|
||||
|
||||
unsigned int lock_lev = RB_GC_VM_LOCK();
|
||||
VALUE id = object_id0(obj);
|
||||
RB_GC_VM_UNLOCK(lock_lev);
|
||||
|
||||
@ -49,6 +49,7 @@ VALUE rb_mod_set_temporary_name(VALUE, VALUE);
|
||||
void rb_obj_replace_fields(VALUE obj, VALUE fields_obj);
|
||||
VALUE rb_obj_complex_fields_build(VALUE obj);
|
||||
VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id);
|
||||
VALUE rb_imemo_fields_field_get(VALUE fields_obj, shape_id_t target_shape_id);
|
||||
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
|
||||
void rb_check_ivar_modifiable(VALUE obj);
|
||||
void rb_ivar_foreach_buffered(VALUE obj, int (*func)(ID name, VALUE val, st_data_t arg), st_data_t arg);
|
||||
|
||||
19
variable.c
19
variable.c
@ -1511,7 +1511,7 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie
|
||||
ROBJECT_SET_EXTENDED(obj, fields_obj);
|
||||
break;
|
||||
case T_DATA:
|
||||
RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, fields_obj);
|
||||
RB_OBJ_ATOMIC_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, fields_obj);
|
||||
break;
|
||||
case T_STRUCT:
|
||||
RSTRUCT_SET_FIELDS_OBJ(obj, fields_obj);
|
||||
@ -1577,13 +1577,10 @@ rb_obj_replace_fields(VALUE obj, VALUE fields_obj)
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_obj_field_get(VALUE obj, shape_id_t target_shape_id)
|
||||
rb_imemo_fields_field_get(VALUE fields_obj, shape_id_t target_shape_id)
|
||||
{
|
||||
RUBY_ASSERT(!SPECIAL_CONST_P(obj));
|
||||
RUBY_ASSERT(RSHAPE_TYPE_P(target_shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(target_shape_id, SHAPE_OBJ_ID));
|
||||
|
||||
VALUE fields_obj = rb_obj_fields(obj, RSHAPE_EDGE_NAME(target_shape_id));
|
||||
|
||||
if (UNLIKELY(rb_shape_complex_p(target_shape_id))) {
|
||||
st_table *fields_hash = rb_imemo_fields_complex_tbl(fields_obj);
|
||||
VALUE value = Qundef;
|
||||
@ -1596,6 +1593,14 @@ rb_obj_field_get(VALUE obj, shape_id_t target_shape_id)
|
||||
return rb_imemo_fields_ptr(fields_obj)[index];
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_obj_field_get(VALUE obj, shape_id_t target_shape_id)
|
||||
{
|
||||
RUBY_ASSERT(!SPECIAL_CONST_P(obj));
|
||||
|
||||
return rb_imemo_fields_field_get(rb_obj_fields(obj, RSHAPE_EDGE_NAME(target_shape_id)), target_shape_id);
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
|
||||
{
|
||||
@ -1964,7 +1969,9 @@ generic_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE va
|
||||
}
|
||||
|
||||
const VALUE original_fields_obj = rb_obj_fields(obj, field_name);
|
||||
VALUE fields_obj = imemo_fields_set(obj, original_fields_obj, target_shape_id, field_name, val, false);
|
||||
// Readers of a shareable object's fields take no lock, so never mutate a published fields_obj.
|
||||
bool concurrent = rb_multi_ractor_p() && RB_OBJ_SHAREABLE_P(obj);
|
||||
VALUE fields_obj = imemo_fields_set(obj, original_fields_obj, target_shape_id, field_name, val, concurrent);
|
||||
|
||||
rb_obj_set_fields(obj, fields_obj, field_name, original_fields_obj);
|
||||
return rb_shape_complex_p(target_shape_id) ? ATTR_INDEX_NOT_SET : RSHAPE_INDEX(target_shape_id);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user