[ruby-core:122646] [Ruby Bug#18733] Heavy GC allocations cause performance issue with Ractor

Issue #18733 has been updated by jhawthorn (John Hawthorn). Subject changed from Ruby GC problems cause performance issue with Ractor to Heavy GC allocations cause performance issue with Ractor This performance gap no longer exists on master. Benchmark from my Ryzen 7 3700X running linux: ``` ❯ ruby -v ruby 3.4.4 (2025-05-14 revision a38531fd3f) +PRISM [x86_64-linux] ❯ RUBY_MAX_CPU=16 ruby test.rb 0.000000 0.003827 19.434401 ( 2.459187) test.rb:20: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. 176.949877 0.005083 176.954960 ( 22.663767) ``` ``` ❯ ruby -v ruby 3.5.0dev (2025-07-02T20:01:24Z master d5f5a56bf2) +PRISM [x86_64-linux] ❯ RUBY_MAX_CPU=16 ruby test.rb 0.000477 0.003464 20.087896 ( 2.532730) test.rb:20: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. 20.272416 0.001401 20.273817 ( 2.576483) ``` I get similar results on my M4 macbook pro A part of this is because `[0,1].include?(x)` no longer allocates (https://github.com/ruby/ruby/commit/1dd40ec18a55ff46f52d0ba44ff5d7923f57c08f), but that was also the case in Ruby 3.4. Modifying the benchmark to use `[0,1].itself.include?(x)` to avoid eliding the Array we can still reproduce the original issue, though it's clear we are making progress. ``` ruby require 'benchmark' def fib(n) return n if [0,1].itself.include?(n) fib(n-1) + fib(n-2) end tp = [] puts Benchmark.measure { 8.times do tp << fork { fib(37) } end tp.each { |t| Process.wait(t) } } puts Benchmark.measure { 8.times.map { Ractor.new { fib(37) } }.each(&:join) } ``` ``` ❯ RUBY_MAX_CPU=16 ruby test.rb 0.000048 0.004020 53.875052 ( 6.815713) test.rb:20: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. 123.640647 27.657227 151.297874 ( 42.308539) ``` I've uploaded a profile here https://share.firefox.dev/44w9adY. We can see that the additional time is spent in GC (sweeping or joining the barrier from a GC step another Ractor initiated) ---------------------------------------- Bug #18733: Heavy GC allocations cause performance issue with Ractor https://bugs.ruby-lang.org/issues/18733#change-113922 * Author: jakit (Jakit Liang) * Status: Assigned * Assignee: ractor * ruby -v: ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [arm64-darwin21] * Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN ---------------------------------------- Code: ``` require 'benchmark' def fib(n) return n if [0,1].include?(n) fib(n-1) + fib(n-2) end tp = [] puts Benchmark.measure { 8.times do tp << fork { fib(37) } end tp.each { |t| Process.wait(t) } } puts Benchmark.measure { 8.times.map { Ractor.new { fib(37) } }.each{ |r| r.take } } ``` Result: |A |B | |--|--| | fork | 0.000264 0.003439 87.181198 ( 11.211349) | | Ractor | 80.292916 15.062559 95.355475 ( 39.569527) | And I found that here's the problem showing on the ActiveMonitor.app:  As you can see, the process of ruby is always using all Performance Cores on my Apple M1 Mac. But there's no one of the Efficiency Cores was in used by ruby Ractor. -- https://bugs.ruby-lang.org/
participants (1)
-
jhawthorn (John Hawthorn)