sigildocs

(sigil gc)

(sigil gc) - Garbage Collector Control

Control the garbage collector for diagnostics, manual collection, and fine-tuning for latency-sensitive applications like games.

(import (sigil gc))

;; Force immediate garbage collection
(gc-collect!)

;; Spread GC work across game frames
(define (game-loop)
  (gc-step! 100)  ; Process up to 100 objects per frame
  (update)
  (render)
  (game-loop))

;; Check memory usage
(gc-allocated-bytes)  ; => 1234567

Exports

gc-collect!procedure

Force a full garbage collection cycle.

Pauses execution until all unreachable objects are freed. Use sparingly—may cause noticeable pauses.

(gc-collect!)

Run a minor (young-generation) collection now.

Collects only recently-allocated objects — typically a sub-millisecond pause, much cheaper than gc-collect!. Latency-sensitive programs (games) can call this at a convenient moment (e.g. end of frame) to keep allocation-triggered minor collections from landing mid-frame:

;; At end of each frame:
(when (> (gc-young-occupancy) 0.6)
  (gc-minor-collect!))

No-op on the marksweep GC backend (no young generation) and while a major collection cycle is in progress.

Frame-boundary collection hint for games.

Runs a minor collection now only if the young generation is at least threshold full (a fraction of the minor-GC trigger, the same scale as gc-young-occupancy). Call it once at the end of each frame to convert allocation-triggered minor pauses that would otherwise land mid-frame into scheduled end-of-frame ones, while skipping the collection entirely on frames where the nursery is still mostly empty.

threshold is optional and defaults to 0.6 (overridable process-wide via the SIGIL_GC_FRAME_HINT_THRESHOLD environment variable). Returns #t if a minor collection ran, #f otherwise.

(define (frame)
  (update!)
  (render!)
  (gc-frame-hint!))       ; collect now if the nursery is >60% full

;; Or with a custom threshold:
(gc-frame-hint! 0.75)

Always #f on the marksweep GC backend (no young generation); such programs should use gc-step! instead.

gc-idle-hint!procedure

Idle-time collection hint for long-running servers.

Call between requests or on an idle timer to move collection work off the request path. It picks the most useful idle work:

  • finishes an in-progress major collection, if one is running;
  • otherwise starts a full major if the heap has grown past a soft fraction of the next-GC threshold (default 0.8, overridable via SIGIL_GC_IDLE_MAJOR_FRACTION), so the expensive pause happens while idle rather than mid-request;
  • otherwise runs a cheap minor collection if there is young garbage;
  • otherwise does nothing.

Returns a symbol naming what ran: major, minor, or none.

(define (serve-loop)
  (handle-next-request!)
  (gc-idle-hint!)          ; reclaim between requests
  (serve-loop))

Pair with SIGIL_GC_HEAP_GROW_FACTOR (e.g. 1.3) for a lower steady-state heap ceiling and SIGIL_GC_MALLOC_TRIM=1 (glibc) to return freed pages to the OS after each major. On the marksweep backend only major/none outcomes occur.

gc-step!procedure

Perform incremental GC work.

Processes up to the specified number of objects, then returns. Use in game loops to spread GC work across frames and avoid pauses.

;; In your game loop:
(gc-step! 100)  ; Process up to 100 objects per frame

Get the current heap size in bytes.

Returns the total memory currently allocated by the garbage collector. Useful for monitoring memory usage and detecting leaks.

(gc-allocated-bytes)
; => 1234567

;; Monitor allocation in a loop
(let ((before (gc-allocated-bytes)))
  (do-work)
  (- (gc-allocated-bytes) before))
; => 5000  ; bytes allocated during do-work

Get young-generation occupancy as a fraction of the minor-GC trigger threshold.

Returns a real number: 0.0 right after a collection, rising toward 1.0 as allocations accumulate (a minor collection triggers automatically when it would exceed 1.0). Cheap enough to poll every frame — it performs no allocation. Always 0.0 on the marksweep GC backend.

(gc-young-occupancy)
; => 0.37
gc-phaseprocedure

Get the current GC phase.

Returns a symbol indicating what the collector is currently doing:

  • idle — not collecting, normal execution
  • marking — tracing live objects
  • sweeping — reclaiming dead objects
(gc-phase)
; => idle

(gc-step! 10)
(gc-phase)
; => marking  ; or sweeping, depending on progress
gc-thresholdprocedure

Get the allocation threshold that triggers the next GC cycle.

When gc-allocated-bytes exceeds this value, a collection begins. The threshold grows automatically as your program uses more memory.

(gc-threshold)
; => 2000000

;; Check how close to triggering GC
(- (gc-threshold) (gc-allocated-bytes))
; => 765433  ; bytes until next GC
gc-statsprocedure

Get detailed GC statistics as an association list.

Fields common to both GC backends:

  • backendgenerational or marksweep. Check this FIRST when diffing stats: the marksweep backend reports none of the generational counters below.
  • bytes-allocated — current GC-tracked heap size
  • rss-bytes — process resident set size (0 on non-Linux); compare against bytes-allocated to observe fragmentation / allocator drift in long-running processes
  • allocations, collections, next-gc

Generational-backend fields include:

  • minor-collections / major-collections and minor-time-ns / major-time-ns
  • minor-max-pause-ns / major-max-pause-ns — worst pause seen
  • minor-pause-hist / major-pause-hist — pause histograms as lists of counts; bucket upper bounds in pause-hist-bounds-ns (the final bucket is the overflow). The counts in each histogram sum to the corresponding collection counter.
  • young-bytes, young-objects-count, tenured-objects-count
  • young-threshold-bytes (+ initial/max/growths), remembered-set and write-barrier counters, reclaimed/promoted byte counters
;; Extract a specific stat
(cdr (assq 'bytes-allocated (gc-stats)))
; => 1234567

;; Fragmentation ratio for a daemon health check
(let ((stats (gc-stats)))
  (/ (cdr (assq 'rss-bytes stats))
     (max 1 (cdr (assq 'bytes-allocated stats)))))