Make rb_gc_impl_writebarrier_remember Ractor-safe

rb_gc_impl_writebarrier_remember is not Ractor safe because it writes to
bitmaps and also pushes onto the mark stack during incremental marking.
We should acquire the VM lock to prevent race conditions.

In the case that the object is not old, there is no performance impact.
However, we can see a performance impact in this microbenchmark where the
object is old:

    4.times.map do
      Ractor.new do
        ary = []

        3.times { GC.start }

        10_000_000.times do |i|
          ary.push(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
          ary.clear
        end
      end
    end.map(&:value)

Before:

    Time (mean ± σ):     682.4 ms ±   5.1 ms    [User: 2564.8 ms, System: 16.0 ms]

After:

    Time (mean ± σ):      5.522 s ±  0.096 s    [User: 8.237 s, System: 7.931 s]

Co-Authored-By: Luke Gruber <luke.gruber@shopify.com>
Co-Authored-By: John Hawthorn <john@hawthorn.email>
This commit is contained in:
Peter Zhu 2025-08-08 15:04:48 -04:00
parent 0ba488d7f5
commit e639e5fd1a

View file

@ -6110,15 +6110,19 @@ rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)
gc_report(1, objspace, "rb_gc_writebarrier_remember: %s\n", rb_obj_info(obj)); gc_report(1, objspace, "rb_gc_writebarrier_remember: %s\n", rb_obj_info(obj));
if (is_incremental_marking(objspace)) { if (is_incremental_marking(objspace) || RVALUE_OLD_P(objspace, obj)) {
if (RVALUE_BLACK_P(objspace, obj)) { int lev = RB_GC_VM_LOCK_NO_BARRIER();
gc_grey(objspace, obj); {
} if (is_incremental_marking(objspace)) {
} if (RVALUE_BLACK_P(objspace, obj)) {
else { gc_grey(objspace, obj);
if (RVALUE_OLD_P(objspace, obj)) { }
rgengc_remember(objspace, obj); }
else if (RVALUE_OLD_P(objspace, obj)) {
rgengc_remember(objspace, obj);
}
} }
RB_GC_VM_UNLOCK_NO_BARRIER(lev);
} }
} }