I just released 0.14.0 of gcry. It’s a conservative mark sweep GC written entirely in Crystal, shipped as a shard. No C code, no compiler patches, no fork. You just add require "gcry" and build with -Dgc_none.
Performance
Same host, median of 3, wrk -c 100 -d 30:
| Workload | gcry vs Boehm | Post-GC RSS |
|---|---|---|
| Kemal /json (Linux) | ~89% throughput | 0.79x Boehm |
| Kemal root (Linux) | ~89% throughput | 0.78x Boehm |
| Kemal /json (macOS) | ~84% throughput | 0.93x Boehm |
| Kemal root (macOS) | ~93% throughput | 1.06x Boehm |
21% less memory than Boehm on Linux Kemal. Same throughput.
What makes gcry different
It’s a conservative, non-moving, stop-the-world collector. Same family as Boehm, so it works with Crystal’s existing ABI. But it’s written in Crystal, which means:
- You can read it. The whole collector is about 9,000 lines of Crystal across 32 files. No C macros, no opaque foreign library.
- You can debug it. Set a breakpoint anywhere. Crystal’s debugger works on it.
- You can change it. Want to experiment with a different marking strategy? Edit a .cr file and rebuild.
Architecture: mmap-backed size classes, fiber stack roots, conservative mark with layout precise opt-in, deferred madvise, and platform specific STW (Linux signals, Mach thread_suspend on macOS).
What’s new in 0.14.0
This release is all about quality infrastructure:
- Debug invariant checker (GCRY_DEBUG_INVARIANTS=1). Validates live_objects counter, freelist consistency, chunk index integrity, block overlap detection. At runtime, on every malloc/free/collect.
- AddressSanitizer and Valgrind on every PR in CI.
- Coverage reports via kcov on every PR.
- Deterministic replay fuzzing with --seed and --replay.
- 24 hour soak tests, thread storm tests, OOM tests, property based heap invariant tests.
- Heap dump (Gcry.dump_heap) streams live objects as NDJSON for leak hunting.
- Trace (GCRY_TRACE=1) traces every collect, malloc, and free.
How to try it
# shard.yml
dependencies:
gcry:
github: sdogruyol/gcry
{% if flag?(:gc_none) %}
require "gcry"
{% end %}
puts "Hello from gcry!"
crystal build -Dgc_none app.cr -o app
No special malloc API. No compiler fork. String, Array, Hash, everything works as usual.
Limitations
gcry is a shard and I’m pushing it as far as I can. But there are things you can’t do from a shard. True precise stack scanning, write barriers, and concurrent collection need compiler support. At some point that line has to be crossed. But for now, the shard approach gets you surprisingly far: Kemal class HTTP at near Boehm perf, layout precise heap scanning, fiber stack scrubbing, and full observability. All without touching the compiler.