Skip to content

runtime (gc): restructure blocks GC metadata#5463

Open
niaow wants to merge 1 commit into
tinygo-org:devfrom
niaow:bitmap-gc
Open

runtime (gc): restructure blocks GC metadata#5463
niaow wants to merge 1 commit into
tinygo-org:devfrom
niaow:bitmap-gc

Conversation

@niaow

@niaow niaow commented Jun 14, 2026

Copy link
Copy Markdown
Member

Originally, the blocks GC simply stored a 2-bit state value for each block with 4 options:

  • umarked head
  • marked head
  • tail (continuation of an object)
  • free

The GC cycled the blocks through these states appropriately. Then the allocator would search for appropriate ranges of free blocks.

This design resulted in excessive memory fragmentation due to the way that the allocator had to search for free ranges. To fix this issue, we created a data structure to track the free ranges that is rebuilt after every GC. This mostly fixed the memory fragmentation issue.

The other issue with this original approach is that it resulted in quadratic performance degredation when scanning free lists. To solve this, we added a header to each heap object to form a linked stack. This ensured that each object only needed to be visited once.

As these improvements were made, TinyGo began practically supporting larger and larger heaps. The current structure where we loop over individual blocks is no longer efficient. We need to change the metadata to support more efficient traversal.

This commit changes the per-block metadata into a pair of bitmaps: an "ends" bitmap and a "visited" bitmap. The "ends" bitmap is used by the marking and sweeping logic to find the end (containing the header) of an object. The "visited" bitmap is to track blocks which have been visited by mark, including both ends and non-ends. Most operations can be performed by scanning over these bitmaps rather than looping over individual blocks.

The "visited" bitmap also fixes the last remaining case for quadratic performance degredation. In the event that many pointers referred to the start of a large object, the marking code would scan across the whole object to find the end every time. The new marking code adds every block between the marked address and the end to the bitmap. Subsequent marks to the same object will detect the already-visited tail and stop early.

@niaow niaow force-pushed the bitmap-gc branch 3 times, most recently from 40a8317 to 4161a04 Compare June 18, 2026 21:46
Originally, the blocks GC simply stored a 2-bit state value for each block with 4 options:
  - umarked head
  - marked head
  - tail (continuation of an object)
  - free

The GC cycled the blocks through these states appropriately.
Then the allocator would search for appropriate ranges of free blocks.

This design resulted in excessive memory fragmentation due to the way that the allocator had to search for free ranges.
To fix this issue, we created a data structure to track the free ranges that is rebuilt after every GC.
This mostly fixed the memory fragmentation issue.

The other issue with this original approach is that it resulted in quadratic performance degredation when scanning free lists.
To solve this, we added a header to each heap object to form a linked stack.
This ensured that each object only needed to be visited once.

As these improvements were made, TinyGo began practically supporting larger and larger heaps.
The current structure where we loop over individual blocks is no longer efficient.
We need to change the metadata to support more efficient traversal.

This commit changes the per-block metadata into a pair of bitmaps: an "ends" bitmap and a "visited" bitmap.
The "ends" bitmap is used by the marking and sweeping logic to find the end (containing the header) of an object.
The "visited" bitmap is to track blocks which have been visited by mark, including both ends and non-ends.
Most operations can be performed by scanning over these bitmaps rather than looping over individual blocks.

The "visited" bitmap also fixes the last remaining case for quadratic performance degredation.
In the event that many pointers referred to the start of a large object, the marking code would scan across the whole object to find the end every time.
The new marking code adds every block between the marked address and the end to the bitmap.
Subsequent marks to the same object will detect the already-visited tail and stop early.
@niaow

niaow commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

This change can improve the sweep performance by nearly 60x in large heaps. The new bitmap scanning code processes whole words per loop iteration (so 64 blocks on 64-bit systems) and the average resulting free range size can easily climb into tens of thousands of blocks on large heaps.

@niaow niaow added the core label Jun 19, 2026
@niaow niaow changed the title WIP: redesign blocks GC runtime (gc): restructure blocks GC metadata Jun 19, 2026
@niaow niaow marked this pull request as ready for review June 19, 2026 20:31
@niaow niaow removed the core label Jun 20, 2026
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.

1 participant