Skip to content

More robust memory reporting for the Ractor harness - #548

Merged
eightbitraptor merged 6 commits into
mainfrom
mvh-luke-improve-ractor-harness
Oct 9, 2026
Merged

eightbitraptor merged 6 commits into
mainfrom
mvh-luke-improve-ractor-harness

Conversation

@eightbitraptor

Copy link
Copy Markdown
Member

A follow-up to the Ractor scenario benchmarks, mostly about making the memory numbers easier to read and more trustworthy.

  • Scenario memory table. run_benchmarks.rb now prints the retained and peak RSS of scenario benchmarks below the GC tables, with one row per Ractor count. Before this they were only in the JSON. The harness now also records the mean and max of each metric next to the median.

  • macOS RSS. get_rss reads ri_resident_size from proc_pid_rusage instead of forking ps, which the peak sampler was doing every 5ms. If that fails it tries /bin/ps before the ps in PATH, because Nix's procps ps can't read RSS on macOS.

  • Collect workers as they finish. The three scenario benchmarks use Ractor.select, so a worker that finishes early isn't kept alive waiting behind a slower one.

  • malloc_trim(0) before measuring retention. On glibc, retained RSS included memory Ruby had already freed. Where libc has no malloc_trim (macOS, musl) the harness says so, and ractor_mem_malloc_trim in the JSON records whether libc has it.

  • RUBY_BENCH_PROFILING=1 skips the GC settle, so a profile shows the scenario and not our retention GCs.

  • Docs. More --ractor-gc examples, including the fact that Ractor benchmarks only run with --category ractor, and a section on profiling with samply.

Because of the trim, Linux retained RSS from before this PR isn't comparable with numbers from after it.

Scenario memory (process RSS, MiB):
bench            ractors       retained med      retained mean               peak
ractor-dead-set        1     3.5  →     3.4     3.5  →     3.4    24.5  →    25.2
ractor-dead-set        2     6.9  →     6.8     6.9  →     6.8    29.3  →    29.7

luke-gruber and others added 6 commits October 9, 2026 18:46
Scenario benchmarks record retained and peak RSS, but run_benchmarks.rb
only kept them in the JSON.

In this commit we add a Scenario memory table below the GC tables, with
one row per Ractor count. It shows the median and mean retained RSS, and
the highest peak any trial reached. The harness now records the mean
and max of each metric next to the median, in whole bytes.

Co-authored-by: eightbitraptor <matt@eightbitraptor.com>
Off Linux, get_rss shells out to ps, which forks on every call, and the
scenario peak sampler calls it every 5ms. A procps ps from Nix earlier
in PATH fails with "requires entitlement", so get_rss fell back to the
lifetime peak RSS.

In this commit macOS reads ri_resident_size from proc_pid_rusage
through Fiddle, and falls back to /bin/ps before the ps in PATH.

Co-authored-by: eightbitraptor <matt@eightbitraptor.com>
The scenario benchmarks took each worker's value in spawn order, so a
worker that finished early stayed alive until every worker ahead of it
had finished.

In this commit they use Ractor.select to take whichever worker finishes
next.

Co-authored-by: eightbitraptor <matt@eightbitraptor.com>
glibc's free() only returns memory to the OS from the top of the heap,
so memory Ruby has freed (Ractor message copies, for example) stays
resident while a live chunk sits above it. Ruby's GC doesn't trim, so
retained RSS included glibc's caching.

In this commit gc_settle calls malloc_trim(0) after the full GCs. Where
libc has no malloc_trim the harness prints why, and
ractor_mem_malloc_trim in the JSON records whether libc has it.

Co-authored-by: eightbitraptor <matt@eightbitraptor.com>
gc_settle runs two full GCs, a malloc_trim and a sleep around every
scenario trial, and they swamp a profile of the scenario itself.

In this commit RUBY_BENCH_PROFILING=1 skips it. Retained RSS then
includes uncollected garbage.

Co-authored-by: eightbitraptor <matt@eightbitraptor.com>
The README showed --ractor-gc only for the whole ractor category, and
didn't say that run_benchmarks.rb skips Ractor harness benchmarks
without --category ractor.

In this commit we add an example that compares two Rubies on one
scenario benchmark, the direct-run form with RUBY_BENCH_RACTOR_GC=1,
and a section on profiling a benchmark with samply.
@eightbitraptor
eightbitraptor requested review from luke-gru and luke-gruber and removed request for luke-gru October 9, 2026 18:34
@eightbitraptor
eightbitraptor merged commit 3cc3712 into main Oct 9, 2026
11 checks passed
@eightbitraptor
eightbitraptor deleted the mvh-luke-improve-ractor-harness branch October 9, 2026 19:00
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.

2 participants