doc/internal/gc.md: update for debt model and new tuning parameters

Document the debt-based GC trigger model, malloc threshold,
step limit, GC.stat, and tuning guide.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-03-14 22:40:15 +09:00
parent 878ecd9b09
commit 4d03f40204
+89 -13
View File
@@ -273,7 +273,7 @@ From Ruby: `GC.generational_mode = true/false`.
`mrb_obj_alloc()` is the core allocation function:
1. If `MRB_GC_STRESS` is defined, run a full GC
2. If `gc->live >= gc->threshold`, run `mrb_incremental_gc()`
2. Increment `gc->gc_debt`; if positive, run `mrb_incremental_gc()`
3. Ensure arena has space (`gc_arena_keep`)
4. Pop an object from the freelist of `gc->free_heaps`
5. If no free pages, allocate a new page (`add_heap`)
@@ -299,18 +299,43 @@ The object's type is set to `MRB_TT_FREE` after freeing.
## Triggering GC
### Automatic
### Debt Model
GC runs automatically when `gc->live >= gc->threshold` during
object allocation. After each cycle:
GC uses a **debt-based feedback model** to balance allocation
rate against collection work. The key field is `gc->gc_debt`
(signed integer):
- **Negative** = credit (GC is ahead, no collection needed)
- **Zero** = balanced
- **Positive** = debt (allocation outpacing collection, GC runs)
Each object allocation increments `gc_debt` by 1. When debt
goes positive, `mrb_incremental_gc()` runs. Each incremental
step decrements debt by `GC_STEP_SIZE` (1024), giving credit
for many future allocations.
When a GC cycle completes, credit is calculated from
`interval_ratio`:
```text
threshold = (live_after_mark / 100) * interval_ratio
credit = (live_after_mark / 100) * interval_ratio - live_after_mark
minimum: GC_STEP_SIZE (1024)
gc_debt = -credit
```
With default `interval_ratio = 200`, GC triggers when live objects
roughly double.
With default `interval_ratio = 200` and 1000 live objects:
`credit = (1000/100)*200 - 1000 = 1000`, so approximately 1000
allocations can occur before the next GC cycle begins.
### Malloc Pressure
When `gc->malloc_threshold` is set (non-zero), the GC also
tracks bytes allocated through `mrb_realloc_simple()` in
`gc->malloc_increase`. When `malloc_increase` exceeds
`malloc_threshold`, the counter resets and an incremental GC
step runs. This captures memory pressure from large buffers
(e.g., long strings) that would otherwise be invisible to the
object-count-based debt model.
### Manual
@@ -333,6 +358,7 @@ From Ruby: `GC.start`.
| `MRB_GC_FIXED_ARENA` | off | Use fixed-size arena |
| `MRB_GC_TURN_OFF_GENERATIONAL` | off | Disable generational mode |
| `MRB_GC_STRESS` | off | Full GC on every allocation (debug) |
| `MRB_GC_STATS` | off | Enable GC statistics counters |
| `MRB_USE_MALLOC_TRIM` | off | Call `malloc_trim()` after full GC |
### Runtime
@@ -340,18 +366,68 @@ From Ruby: `GC.start`.
From Ruby code:
```ruby
GC.interval_ratio = 200 # threshold = live * ratio / 100
GC.step_ratio = 200 # objects per incremental step
GC.interval_ratio = 200 # controls debt credit after GC cycle
GC.step_ratio = 200 # objects per incremental step
GC.step_limit = 0 # 0=unlimited, >0=absolute step cap
GC.malloc_threshold = 0 # 0=disabled, >0=bytes to trigger GC
GC.generational_mode = true
GC.start # force full GC
GC.enable # re-enable GC
GC.disable # disable GC
GC.start # force full GC
GC.enable # re-enable GC
GC.disable # disable GC
```
### GC Statistics
`GC.stat` returns a Hash with GC state and statistics:
```ruby
GC.stat
# => {
# :live => 5432, # live object count
# :threshold => 1024, # GC credit (positive=headroom)
# :state => 0, # 0=root, 1=marking, 2=sweeping
# :generational => true, # generational mode enabled
# :full => false, # major GC in progress
# :step_limit => 0, # current step limit setting
# :malloc_increase => 8192, # malloc bytes since last cycle
# :malloc_threshold => 0, # current malloc threshold setting
# }
```
With `MRB_GC_STATS` enabled, additional keys are available:
```ruby
# :total => 15, # total GC invocations
# :minor => 12, # minor GC count
# :major => 3, # major GC count
```
### Tuning Guide
**`interval_ratio`** (default 200): Controls how many allocations
occur between GC cycles. Higher values reduce GC frequency but
increase peak memory. The debt credit after each cycle is
`(live_after_mark / 100) * interval_ratio - live_after_mark`.
**`step_ratio`** (default 200): Controls how much work each
incremental step performs. Higher values make each step larger,
reducing total GC overhead but increasing individual pause times.
**`step_limit`** (default 0, unlimited): Caps the maximum work
per incremental step regardless of `step_ratio`. Useful for
real-time applications that need bounded pause times. The
effective step size is `min(step_ratio calculation, step_limit)`.
**`malloc_threshold`** (default 0, disabled): Triggers GC when
cumulative `malloc`/`realloc` bytes exceed this threshold. Useful
when applications allocate large buffers (strings, data objects)
that create memory pressure without proportional object count
increase.
## Source Files
| File | Contents |
| -------------------- | --------------------------------- |
| `src/gc.c` | GC implementation (~1400 lines) |
| `src/gc.c` | GC implementation |
| `include/mruby/gc.h` | `mrb_gc` structure, public GC API |
| `include/mruby.h` | Arena save/restore macros |