8000 shape.c: Implement a lock-free version of get_next_shape_internal by casperisfine · Pull Request #13441 · ruby/ruby · GitHub
[go: up one dir, main page]

Skip to content

shape.c: Implement a lock-free version of get_next_shape_internal #13441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 2, 2025

Conversation

casperisfine
Copy link
Contributor

Whenever we run into an inline cache miss when we try to set an ivar, we may need to take the global lock, just to be able to lookup inside shape->edges.

To solve that, when we're in multi-ractor mode, we can treat the shape->edges as immutable. When we need to add a new edge, we first copy the table, and then replace it with CAS.

This increases memory allocations, however we expect that creating new transitions becomes increasingly rare over time.

class A
  def initialize(bool)
    @a = 1
    if bool
      @b = 2
    else
      @c = 3
    end
  end

  def test
    @d = 4
  end
end

def bench(iterations)
  i = iterations
  while i > 0
    A.new(true).test
    A.new(false).test
    i -= 1
  end
end

if ARGV.first == "ractor"
  ractors = 8.times.map do
    Ractor.new do
      bench(20_000_000 / 8)
    end
  end
  ractors.each(&:take)
else
  bench(20_000_000)
end

The above benchmark takes 27 seconds in Ractor mode on Ruby 3.4, and only 1.7s with this branch.

cc @etiennebarrie @jhawthorn @tenderlove @luke-gruber

@casperisfine casperisfine force-pushed the shape-next-gc branch 4 times, most recently from 314d196 to 8246a67 Compare May 26, 2025 14:06
Copy link
Member
@jhawthorn jhawthorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! The atomic logic reads as correct to me

@byroot byroot enabled auto-merge (rebase) May 27, 2025 06:54
@casperisfine
Copy link
Contributor Author

I got a CI failure on rebase that seems legit:

librubygc.default.so(gc_is_moveable_obj+0x0) [0x7faa331993d6] ../../../src/gc/default/default.c:3248
librubygc.default.so(gc_compact_plane) ../../../src/gc/default/default.c:5526
/lib/x86_64-linux-gnu/libc.so.6(0x7faa32e45330) [0x7faa32e45330]
ruby(hash_table_index+0x0) [0x55a8849be748] ../src/symbol.h:72
ruby(rb_id_table_lookup) ../src/id_table.c:237
ruby(rb_managed_id_table_lookup) ../src/id_table.c:399
ruby(shape_traverse_from_new_root+0x8) [0x55a884973f59] ../src/shape.c:1076
ruby(shape_traverse_from_new_root) ../src/shape.c:1051
ruby(shape_traverse_from_new_root) ../src/shape.c:1051
ruby(shape_traverse_from_new_root) ../src/shape.c:1051
ruby(shape_traverse_from_new_root+0x15) [0x55a884974e70] ../src/shape.c:1051
ruby(shape_traverse_from_new_root) ../src/shape.c:1051
ruby(rb_shape_traverse_from_new_root) ../src/shape.c:1100
ruby(rb_gc_rebuild_shape+0x29) [0x55a884863ad9] ../src/gc.c:388
librubygc.default.so(gc_compact_move+0x145) [0x7faa331a1294] ../../../src/gc/default/default.c:5468
librubygc.default.so(gc_compact_plane) ../../../src/gc/default/default.c:5527
librubygc.default.so(gc_compact_page+0x99) [0x7faa331a1cec] ../../../src/gc/default/default.c:5565
librubygc.default.so(gc_sweep_compact) ../../../src/gc/default/default.c:5607
librubygc.default.so(gc_sweep) ../../../src/gc/default/default.c:4074
librubygc.default.so(gc_start+0xb1b) [0x7faa331a354b] ../../../src/gc/default/default.c:6426
librubygc.default.so(garbage_collect+0x66) [0x7faa331a4001] ../../../src/gc/default/default.c:6307
librubygc.default.so(newobj_slowpath) ../../../src/gc/default/default.c:2433
librubygc.default.so(newobj_slowpath_wb_protected) ../../../src/gc/default/default.c:2455
librubygc.default.so(rb_gc_impl_new_obj+0xd8) [0x7faa331a4648] ../../../src/gc/default/default.c:2492

It seems we're rebuilding an object shape during compaction. Not sure exactly when happened, but I suppose shape->edges might have been moved. I'll have to consult with @peterzhu2118 or @eightbitraptor to see how we could legally do this.

@byroot byroot disabled auto-merge May 27, 2025 07:04
Whenever we run into an inline cache miss when we try to set
an ivar, we may need to take the global lock, just to be able to
lookup inside `shape->edges`.

To solve that, when we're in multi-ractor mode, we can treat
the `shape->edges` as immutable. When we need to add a new
edge, we first copy the table, and then replace it with
CAS.

This increases memory allocations, however we expect that
creating new transitions becomes increasingly rare over time.

```ruby
class A
  def initialize(bool)
    @A = 1
    if bool
      @b = 2
    else
      @c = 3
    end
  end

  def test
    @d = 4
  end
end

def bench(iterations)
  i = iterations
  while i > 0
    A.new(true).test
    A.new(false).test
    i -= 1
  end
end

if ARGV.first == "ractor"
  ractors = 8.times.map do
    Ractor.new do
      bench(20_000_000 / 8)
    end
  end
  ractors.each(&:take)
else
  bench(20_000_000)
end
```

The above benchmark takes 27 seconds in Ractor mode on Ruby 3.4,
and only 1.7s with this branch.

Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>
@byroot
Copy link
Member
byroot commented Jun 2, 2025

I suppose shape->edges might have been moved.

It seems I was right. For now I made it so these objects are pinned, which fixes the issue. That's not ideal, but I have no better solution short term.

My hope however is that if we move on with #13289, ultimately there would be no SHAPE_T_OBJECT, the heap_id data would just be a tag inside the shape_id, which means GC would no longer need to rebuild shapes, hence we'd no longer need to pin these.

But that's multiple big "ifs".

@byroot byroot requested a review from peterzhu2118 June 2, 2025 13:14
@byroot byroot merged commit db2cfeb into ruby:master Jun 2, 2025
82 checks passed 7D92
@casperisfine casperisfine deleted the shape-next-gc branch June 2, 2025 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0