Bug #22455
openPer-Ractor GC: an idle Ractor costs ~100 KB of memory (17 KB on 4.0)
Description
Description¶
With the per-Ractor GC, an idle Ractor (blocked in Ractor.receive)
costs ~101 KB of RSS against ~17 KB on 4.0.7. In our server, a
connection's Ractor, with a few threads and a few hundred live objects,
went from ~55–80 KB on 4.0 to ~590 KB on 4.1. 25,000 connections need
~15 GB instead of ~1.4 GB.
The per-Ractor GC removes our main 4.0 problem: every GC used to stop
and mark all Ractors, so its pauses grew with the number of
connections. A Ractor per connection is the shape it should favour.
At this memory cost, that shape is hard to use for many connections.
Is the floor per Ractor (heap pages per size class?) expected, and can
it be lowered, for example by allocating a Ractor's pages lazily or
sharing partly-used pages?
Reproduction¶
# 4,000 Ractors blocked in Ractor.receive: memory each, and the cost of a full GC.
Warning[:experimental] = false
def rss_mb = File.read("/proc/self/status")[/VmRSS:\s+(\d+)/, 1].to_i / 1024.0
def now_ms = Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000
GC.start
base = rss_mb
ractors = Array.new(4000) { Ractor.new { Ractor.receive } }
sleep 1
puts format("rss per idle Ractor: %.0f KB", (rss_mb - base) * 1024 / ractors.size)
3.times do
t = now_ms
GC.start
puts format("GC.start: %.1f ms", now_ms - t)
end
Results¶
Same machine as above.
| 4.0.7 | 4.1.0-preview1 | |
|---|---|---|
| RSS per idle Ractor | 17 KB | 101 KB |
GC.start (global) with 4,000 idle Ractors |
10–21 ms | 43–141 ms |
The global GC's cost matters together with Bug #22454: if global GCs are
what reclaims exited Ractors, a server that holds thousands of Ractors
pays this pause each time. We saw single global GCs of 0.8–3.9 s, most
of it sweeping, in a server holding 2,000–5,000 connection Ractors.
No data to display