summaryrefslogtreecommitdiff
path: root/notes/261002-free-bitmap.md
diff options
context:
space:
mode:
authorTaylan Kammer <taylan.kammer@gmail.com>2026-10-02 14:34:10 +0200
committerTaylan Kammer <taylan.kammer@gmail.com>2026-10-02 14:34:10 +0200
commit2d2078362a1efff46547e630a70075f3fd493458 (patch)
tree0f331c58db1f1c28fffa3f61f3919fdf48080dd5 /notes/261002-free-bitmap.md
parentd780c0e48b25c81d41fe93faf25db4b62a8954c8 (diff)
Update last note.
Diffstat (limited to 'notes/261002-free-bitmap.md')
-rw-r--r--notes/261002-free-bitmap.md109
1 files changed, 109 insertions, 0 deletions
diff --git a/notes/261002-free-bitmap.md b/notes/261002-free-bitmap.md
index eebccc8..10edf25 100644
--- a/notes/261002-free-bitmap.md
+++ b/notes/261002-free-bitmap.md
@@ -216,3 +216,112 @@ entails more broadly.
* When the GC sweeps, there's a lower limit N, below which it refuses
to free slots in a block, so it doesn't add free-list entries with
very few slots.
+
+
+## Update
+
+It's not nice if we have to use a CAS loop to pop an entry off of the
+global free-list only to receive 4 or so usable slots.
+
+After mulling over it a bit, I came up with a neat solution. And it
+allows bringing "N" (see above) all the way down to 2 if desired, for
+minimal memory waste.
+
+Given that `N >= 2`, each free-list entry represents a block that has
+at least one *other* free slot than the one being used to represent
+the entry. As such, we can combine the bitmap strategy with the one
+originally described here:
+
+* [Using Meta Alloc, Attempt 2](260920-use-alloc-2.html): See section
+ about Free-List representation, with the neat ASCII graphic.
+
+There, the idea was that each entry points not only to the next node
+of the main free-list, but also starts a sub-linked-list (with some
+inline slot pointers per node, for further density) so that popping
+one free-list entry with a single CAS loop gives you an arbitrarily
+sized set of free slots to put into your thread-local cache.
+
+Since we now use 4 bytes for a bitmap, we don't have place (in the
+smallest size class) for a further sub-list head index. However,
+since the bitmap informs us of at least one other free slot, we can
+simply use that!
+
+This is quite a complicated structure, but to sum it up:
+
+* Each main free-list entry points to a slot of at least 8 bytes; it
+ contains a 4-byte index to the next main entry, and a 4-byte bitmap
+ declaring the presence of *at least one other* slot within the same
+ block that belongs to this free slot set.
+
+* The >= 1 other slots each contain two 4-byte indexes to two other
+ slots, each representing a sub-set of free slots under this main
+ free slot set.
+
+Assuming 8-byte slots:
+
+ Root{ head_idx: u32, aba_counter: u32 }
+ |
+ | Block 0x223344A0 {
+ | Slot 0x223344A1 { occupied }
+ | Slot 0x223344A2 { occupied }
+ | ...
+ `----> Slot 0x223344A8 { ("Main Head Slot")
+ next_head_index: u32
+ block_occupancy: u32 --------. (intra-block links)
+ } |
+ Slot 0x223344A9 { occupied } |
+ Slot 0x223344B0 { occupied } |
+ ... |
+ Slot 0x223344B4 { <------------| ("Carrier Slot")
+ subset1_head_index: u32 |
+ subset2_head_index: u32 |
+ } |
+ ... |
+ Slot 0x223344B7 { <------------' ("Carrier Slot")
+ .------- subset1_head_index: u32 (inter-block link)
+ | subset2_head_index: u32 (inter-block link)
+ | }
+ | ...
+ | }
+ |
+ | Block 0x556677D0 {
+ | Slot 0x556677D1 { occupied }
+ | Slot 0x556677D2 { occupied }
+ | ...
+ `----> Slot 0x556677D5 { ("Sub Head Slot")
+ next_subhead_index: u32
+ block_occupancy: u32 ----.
+ } |
+ ... |
+ Slot 0x556677D9 { <--------' ("Carrier Slot")
+ subset1_head_index: u32
+ subset2_head_index: u32
+ }
+ ...
+ }
+
+Yeah, I'm a little proud of this graphic.
+
+For minimum free slots N per block, each main head or sub head slot
+points at N-1 carrier slots, each of which point to exactly two sub
+head slots.
+
+We could optimize this further for size classes greater than 8 bytes,
+but I really don't think it's worth the added complexity and looping
+construct (i.e. branch instructions) needed to implement that.
+
+I'm too lazy to calculate the worst-case number of pointer hops for
+the pessimal choice of N = 2 (optimal to minimize waste, pessimal to
+minimize pointer chasing) but it ought to be something acceptable.
+
+Given upper bound M for the number of free slots represented by each
+such tree of free slots, the time cost of "unfolding" it is amortized
+`O(1)` if we want `gc_alloc()` to do so eagerly, though we could also
+leave it in tree form in thread-local caches; that would just require
+changing the strategy used to handle manual free calls. For example,
+we could simply have a separate flat array of "immediate reuse" slot
+indexes. Just need to think about how it interacts with the bitmap
+used to track allocation status; either you make sure to atomically
+clear the bit for the individual freed slot, or you leave it dirty
+while making sure that these local "immediate reuse" buffers are
+considered part of a thread's root set during marking.