Adds a second asm build (asm/uncommonlisp-gc) behind the GC_NAIVE
assembler flag, providing the benchmark baseline we previously had
no data for. Same binary, same surface, different allocator:
- 8-byte header per heap block (size << 1 | mark), placed at -8
from the tagged pointer so existing untag + offset accesses
stay unchanged.
- Chunk list tracked in a side array, letting sweep walk every
mmap'd region by header-chained blocks instead of guessing.
- Free list rebuilt each sweep, first-fit alloc with split on
large-leftover (>= 24 bytes).
- Mark phase enumerates five root classes: %r14 (global env,
untagged chain), sym_else_val, sym_table entries, every
sym_hash_bucket chain, and a conservative scan from current
%rsp to the initial stack_top captured at _start. The stack
scan runs twice per word — once as a tagged value, once as a
potential untagged env-node pointer (size-guarded to 24 bytes
so it can't walk off a wrong-size block).
- Transitive marking via an explicit 16K-entry mark stack;
gc_mark_env walks untagged env chains from %r14 and from every
closure's env field.
- heap_alloc preserves the non-GC ABI (only %rax clobbered) so
existing callers like bi_append, which holds state in %rcx
across make_pair, keep working.
- Overflow path uses check-then-write bumps and pads the old
chunk's tail with a single dead block before growing, so sweep
never walks into uninitialized mmap'd memory.
- HEAP_SIZE shrinks to 1 MB under GC_NAIVE so the collector
actually runs on ordinary workloads.
- Two diagnostic builtins in the GC build: (gc-collect) to force
a collection, (gc-stats) -> (collections . live-bytes).
Control-group bench (examples/bench-gc-memory.lsp, 2000 iterations
of build-sum-discard over 200-element lists, i5-8350U):
tier time_ms peak_rss final_rss
asm no-GC 1097 133.9 MB 133.9 MB (grows, never shrinks)
asm naive GC 1431 1.1 MB 1.1 MB (steady state)
124x less memory at a ~30% throughput cost. That is the number we
were guessing at before. Reproduce: make bench-gc.
Tests: 137 asm (no-GC) + 137 asm (GC) + 189 shared functional pass.
The two asm builds are tested independently via UNCOMMONLISP_BIN in
asm/test.sh; asm/Makefile now builds both and exposes a test-gc
target.
33 lines
1.1 KiB
Text
33 lines
1.1 KiB
Text
;;; bench-gc-memory.lsp — sustained allocation workload that exposes
|
|
;;; the bump-only tier's unbounded growth vs the naive GC tier's
|
|
;;; bounded steady-state. The body builds a throwaway list of K pairs
|
|
;;; and sums it, repeated N times. Each iteration's list is
|
|
;;; unreachable after the sum, so a collecting heap stays flat while
|
|
;;; a bump-only heap grows linearly.
|
|
;;;
|
|
;;; Wall-clock timing is reported from inside; peak RSS is sampled
|
|
;;; by the wrapper (tests/bench-gc-memory.sh).
|
|
|
|
(define K 200)
|
|
(define N 2000)
|
|
|
|
(define (build-list k acc)
|
|
(if (= k 0) acc (build-list (- k 1) (cons k acc))))
|
|
|
|
(define (sum-list lst acc)
|
|
(if (null? lst) acc (sum-list (cdr lst) (+ acc (car lst)))))
|
|
|
|
(define total 0)
|
|
(define i 0)
|
|
|
|
(define (step)
|
|
(if (>= i N) total
|
|
(begin
|
|
(set! total (+ total (sum-list (build-list K '()) 0)))
|
|
(set! i (+ i 1))
|
|
(step))))
|
|
|
|
(define t0 (current-time-ms))
|
|
(display "workload N=") (display N) (display " K=") (display K) (newline)
|
|
(display "checksum=") (display (step)) (newline)
|
|
(display "time_ms=") (display (- (current-time-ms) t0)) (newline)
|