Bug #18733
open
Heavy GC allocations cause performance issue with Ractor
Description
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.
Updated by ko1 (Koichi Sasada) over 3 years ago
Thank you for your report.
On my WSL2 environment with 12 cores, ruby 3.2.0dev (2022-04-15T04:24:48Z master 92614111c0) [x86_64-linux]
shows worse results.
0.003304 0.000000 102.404055 ( 13.083221)
296.139861 262.090810 558.230671 (278.664518)
However, I modified it with
def fib(n)
return n if n < 2 # `[0,1].include?(n)` generates an Array object
fib(n-1) + fib(n-2)
end
it shows
0.000000 0.003886 31.579734 ( 4.092415)
31.779964 0.003833 31.783797 ( 4.114323)
maybe because of object allocation (GC) causes contention and the system determines it should not use performance cores.
(BTW I can't see figure "ruby_bug_with_m1")
The conclusion is, the reason of this issue is the implementation of object allocation (GC).
Updated by jakit (Jakit Liang) over 3 years ago
- Subject changed from Ruby always using Performance Cores with Apple M1 to Ruby GC problems cause performance issue with Ractor
Updated by jakit (Jakit Liang) over 3 years ago
Thanks much! :)
And I change the topic to GC problem instead problem of m1.
Hope the memory management will get improvement. ;)
Updated by Mark24 (Yang Zhang) over 3 years ago
One problem I found was that when I ran the same script on different Ruby versions, there was performance gap between them.
require 'benchmark'
def fib(n)
return n if n < 2
fib(n-1) + fib(n-2)
end
puts Benchmark.measure {
8.times.map {
Ractor.new{ fib(38) }
}.each{|r| r.take}
}
# Device Mac mini (M1, 2020)
# ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21]
# 33.873394 0.143453 34.016847 ( 4.727655)
# 33.749938 0.153633 33.903571 ( 4.749335)
# 34.343544 0.168297 34.511841 ( 4.846679)
# 33.873749 0.148050 34.021799 ( 4.753797)
# 34.144618 0.177153 34.321771 ( 4.861416)
# time avg: 4.7877764
# ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [arm64-darwin21]
# 43.994316 0.224457 44.218773 ( 6.309634)
# 44.265488 0.192328 44.457816 ( 6.124617)
# 44.194585 0.215987 44.410572 ( 6.195150)
# 43.846945 0.219032 44.065977 ( 6.259834)
# 44.076493 0.213183 44.289676 ( 6.234526)
# time avg: 6.224752199999999
# ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]
# 43.657412 0.241501 43.898913 ( 6.385710)
# 43.986863 0.230514 44.217377 ( 6.370350)
# 43.737608 0.233663 43.971271 ( 6.370617)
# 44.182188 0.210544 44.392732 ( 6.197169)
# 43.907983 0.239740 44.147723 ( 6.278601)
# time avg: 6.3204894000000005
Apple M1 CPU 8 cores all work.
5 times avg used time¶
name | avg times | compare |
---|---|---|
ruby310 | 4.7877764 | 100% |
ruby311 | 6.2247522 | 130% |
ruby312 | 6.3204894 | 132% |
Maybe this could some help.
Updated by hsbt (Hiroshi SHIBATA) 2 months ago
- Status changed from Open to Assigned
Updated by jhawthorn (John Hawthorn) 13 days ago
· Edited
- Subject changed from Ruby GC problems cause performance issue with Ractor to Heavy GC allocations cause performance issue with Ractor
This performance gap in the original benchmark 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.
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)