In Ruby, what does a memory_profiler report show about allocated versus retained objects, and when do you reach for it instead of a sampling profiler?
answer
- MemoryProfiler.report { } .pretty_print
- allocated: created during the block
- retained: still alive after GC
- by gem, file, location, class
- GC off while tracing: heavy overhead
basics
~20 sMemoryProfiler.report { code }.pretty_print lists every object the block allocated and those still retained after a forced GC, grouped by gem, file, location and class, with counts and bytes. Use it to find allocation hot spots and leaks, not for CPU time.
solid answer
~40 s`require "memory_profiler"`, then `MemoryProfiler.report { code }.pretty_print`. It traces every allocation in the block with `ObjectSpace.trace_object_allocations`, with GC disabled while it runs, then forces full collections and checks which of those objects survived. **Allocated** means created during the block, which drives GC work; **retained** means still reachable afterwards, which is what grows memory. Each is reported as memory and object counts **by gem, file, location and class**, plus the most common allocated and retained Strings. Sizes come from `ObjectSpace.memsize_of`, so treat bytes as estimates. It is exhaustive and slow, so run it on one representative unit of work, not in production. A sampling profiler answers *where CPU or wall time goes*; memory_profiler answers *which line created these objects and which ones stayed*.
code
ruby · 9 linesrequire "memory_profiler"
report = MemoryProfiler.report(top: 10, allow_files: "app/") do
rows.each { |r| out << format_row(r) }
end
puts report.total_allocated # objects created in the block
puts report.total_retained # of those, still alive after GC
report.pretty_print(scale_bytes: true, to_file: "tmp/mem.txt")go deeper
Recall that memory_profiler reports allocated and retained objects for a block, grouped by gem, file, location and class.
Explain allocated versus retained, why GC is disabled during tracing, and why byte sizes are estimates.
Show you pair a CPU profile's GC share with a memory_profiler run on one unit of work to name the allocating lines.
Decide where exhaustive allocation profiling fits in the team's workflow versus sampled allocation data from production-like runs.
## What it measures **memory_profiler** is a gem (Ruby 3.1 and later) that records **every object allocated** inside a block and reports where each came from. Unlike a sampler, it is exhaustive. ```ruby require "memory_profiler" report = MemoryProfiler.report(top: 20) do ExportRow.render_all(rows) # one representative unit of work end report.pretty_print(scale_bytes: true) ``` There is also a `MemoryProfiler.start` / `MemoryProfiler.stop` pair, usable once per report, and a command-line runner, `ruby-memory-profiler run -- ruby script.rb`. ## How it works, and why that matters The reporter's own code shows the sequence: 1. run several full `GC.start` calls, then **`GC.disable`**, and note the current GC generation; 2. `ObjectSpace.trace_object_allocations_start`, run your block, then stop tracing; 3. **`GC.enable`** and force full collections again; 4. walk the heap: objects from the block that still exist are **retained**. Consequences: - The block runs with **GC off**, so a large block can use a lot of memory while profiled. - Tracing every allocation makes the code **much slower** than normal; profile one request or one batch, not a long job, and never leave it on in production. - Byte sizes come from `ObjectSpace.memsize_of`, which its own documentation calls a hint; object counts are exact, bytes are estimates. ## Reading the report The report has sections for each combination of: | Axis | Values | |---|---| | Type | **allocated** (created during the block) and **retained** (still alive after the forced GC) | | Metric | **memory** (bytes) and **objects** (count) | | Grouped by | **gem**, **file**, **location** (file:line) and **class** | After those come **allocated String** and **retained String** reports listing the most frequently created string values and where they came from. How to use it: - **Allocated by location** is the list of lines to optimise for GC pressure: a line inside a per-row loop that allocates three Strings per row shows up at the top. - **Retained** objects are the suspects for memory growth: if something the block should have discarded is retained, find what still references it. - **By gem** separates your code from a dependency's; `allow_files:` and `ignore_files:` narrow the trace. - **Allocated strings** often reveal repeated identical values that could be built once. ## When to use it instead of a sampling profiler | Question | Tool | |---|---| | Where does CPU or wall time go? | a sampling profiler such as stackprof or vernier | | Which lines allocate the most objects? | memory_profiler (exact) or stackprof `:object` mode (sampled) | | What stays alive after this code runs? | memory_profiler's retained section, or vernier's `trace_retained` | | Is the whole process growing over hours? | process-level GC counters first, then memory_profiler on a suspect unit | A common flow: a CPU profile shows a large GC share, and memory_profiler then names the lines responsible. ## Summary memory_profiler traces every allocation in one block with GC off, then reports allocated and retained memory and objects by gem, file, location and class. It is exact about counts, approximate about bytes, slow by design, and the tool for "who allocates this, and what stays".
- Why can memory_profiler make a block use far more memory than it does normally?The reporter calls `GC.disable` before running the block and re-enables collection only afterwards, so nothing allocated during the block is freed while it runs. A block that normally churns through garbage will hold all of it at once. Profile a small, representative unit of work.
- A line shows high allocated memory but nothing retained; is it a problem?It is not a leak: everything it creates becomes garbage. It can still be a performance problem, because allocation drives collection work. If the line sits in a hot loop, reducing what it allocates cuts GC time; if it runs rarely, leave it.
saying these in an interview costs you the question
- Retained objects are simply all objects the block allocated
- memory_profiler is cheap enough to leave enabled in production
- memory_profiler shows where CPU time is spent
- Byte counts in the report are exact heap measurements
- Garbage collection runs normally while memory_profiler traces the block