Skip to content

druntime: opt-in thread-local GC (tgc) - #23514

Open
AMDphreak wants to merge 1 commit into
dlang:masterfrom
dlang-supplemental:feature/tgc
Open

druntime: opt-in thread-local GC (tgc)#23514
AMDphreak wants to merge 1 commit into
dlang:masterfrom
dlang-supplemental:feature/tgc

Conversation

@AMDphreak

Copy link
Copy Markdown

Summary

Adds an opt-in thread-local garbage collector registered as tgc (--DRT-gcopt=gc:tgc).

Each attached thread owns a private heap arena. Collection scans/sweeps only that thread (stack, TLS, local blocks) and does not call thread_suspendAll. Detached @nogc threads remain unpaused. The default conservative GC is unchanged.

Informal side-name in prose: "realtime GC". Registered name: tgc.

Motivation

D isolates TLS data and offers actor-style std.concurrency, but the GC heap is still process-global. A collection anywhere stop-the-world pauses all registered threads, including pure @nogc realtime workers. An opt-in per-thread collector lets mixed GC / @nogc apps keep predictable latency on critical threads.

Usage

./app --DRT-gcopt=gc:tgc

v1 limits

  • Prefer copy / immutable message passing across threads.
  • Ownership transfer uses a remote free list on the owning thread.
  • Unrestricted shared GC pointers across heaps unsupported.
  • Partitioned shared regions = Phase 2 (documented, not implemented).
  • Prototype quality: smoke-tested (druntime/test/gc/tgc.d); not a production realtime guarantee without further benchmarks.

Design article

https://dlang-supplemental.github.io/docs/docs/blog/thread-local-gc-tgc.html

(Also: news stub https://dlang-supplemental.github.io/docs/docs/news/tgc-upstream-pr.html after docs deploy.)

Test plan

  • make -C druntime builds with new module
  • druntime/test/gc including tgc with --DRT-gcopt=gc:tgc on Windows
  • CI on this PR
  • Reviewer feedback on API surface / Phase 2 scope

Introduce --DRT-gcopt=gc:tgc with per-thread heaps and local collection
that avoids global stop-the-world pauses, for mixed GC/@nogc realtime work.

Co-authored-by: Cursor <cursoragent@cursor.com>
AMDphreak added a commit to dlang-supplemental/docs that referenced this pull request Aug 2, 2026
Cross-link the tgc announcement to the opened pull request.

Co-authored-by: Cursor <cursoragent@cursor.com>
AMDphreak added a commit to dlang-supplemental/docs that referenced this pull request Aug 2, 2026
* Add Blog and News sections with tgc design article

Introduce Antora Blog/News (separate from Changelog) and publish the
thread-local GC (tgc) concept post plus upstream announcement stub.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Link news article to upstream dlang/dmd#23514

Cross-link the tgc announcement to the opened pull request.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
@AMDphreak

Copy link
Copy Markdown
Author

Design write-up and announcement

Opt-in only via --DRT-gcopt=gc:tgc. Happy to iterate on v1 limits / Phase 2 shared regions based on review.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

DMD perf check

Metric Base PR delta
compile hello.d (instr) 215.9 M 215.9 M 0.00%
compile hello.d -O (instr) 234.5 M 234.5 M 0.00%
compile Phobos (instr) 5,077.3 M 5,077.3 M 0.00%
compile Phobos codegen (instr) 1,453.3 M 1,453.3 M 0.00%
compile vibe.d (instr) 15,136.3 M 15,136.4 M 0.00%
dmd binary size (stripped) 6.92 MB 6.92 MB 0.00%
hello binary size 0.72 MB 0.74 MB +2.86%
peak RSS (compile hello.d) 44 MB 44 MB -0.34%
peak RSS (compile Phobos) 633 MB 633 MB -0.06%
peak RSS (compile vibe.d) 1981 MB 1980 MB -0.03%

@rainers

rainers commented Aug 3, 2026

Copy link
Copy Markdown
Member

Interesting experiment, not sure how far you can get with that using the current D runtime, though. Note for example that immutable data (e.g. strings) is implicitly shared between threads and message passing often takes advantage of that. This results in cross-thread pointer references that can cause premature collection.

One way to get an idea of how far this GC can be used is to enable it by default in this PR and check the results of the test suite.

There are a couple of implementation details that might also need some work:

  • every allocation has an extra header of 40 bytes (on a 64-bit system) on top of the C alloc overhead. That is quite a lot.
  • findBlock uses linear search, that becomes slow rather early, especially bad for collection
  • findBlock is used across multiple threads, that's not thread-safe if the owner thread adds or removes entries.
  • cross-thread freeing using the remoteList seems strange to begin with, given that the pointer isn't supposed to exist in other threads
  • most API calls need to process the remoteList in case there is some synchronization outside of the GC that expects sequential behavior
  • not supporting the array API (getArrayUsed et al) wastes a lot of memory as array appending always allocates new memory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants