Learning

GPU Programming · Reference

GPU lifecycle cheat sheet

Bounded queues, atomic slot reservation, dense compaction, and ping-pong buffers.

01 The invariants

Invariant Meaning
active_count ≤ capacity The logical range always fits the fixed allocation.
Live range is [0, active_count) Every record in the logical range is live after compaction; there are no holes.
Spare range is [active_count, capacity) No pass may treat spare records as active merely because the bytes exist.
Overflow is observable Rejected records increment telemetry; no write wraps or escapes the allocation.
Index is not identity Unstable compaction may move a body to a different slot every lifecycle pass.
next logical count min(survivors + requested births, capacity)

02 The five-dispatch pass

Order Phase Dispatch size Barrier after?
1 Reset next_count and overflow One workgroup Yes
2 Mark removal indices dead ceil(remove_count / local_size) Yes
3 Compact live source → destination ceil(capacity / local_size) Yes
4 Append addition records ceil(add_count / local_size) Yes
5 Clamp and publish active_count One workgroup Before a dependent consumer

Skip an empty queue phase rather than dispatching zero workgroups. Barriers are for dependencies, not decoration: if the next dispatch consumes the previous dispatch’s buffer writes, call compute_list_add_barrier() between them.

03 The slot-reservation pattern

uint slot = atomicAdd(counts.next_count, 1u);
if (slot < capacity) {
    destination.data[slot] = record;
} else {
    atomicAdd(counts.overflow_count, 1u);
}

04 Persistent resources

Resource Lifetime / role
Body A, Body B Fixed-capacity ping-pong pair; source and destination swap after each pass.
Addition queue Fixed-capacity initialization records uploaded or produced before append.
Removal queue Indices or handles to mark dead; delayed CPU indices require validation.
Counter buffer active_count, next_count, overflow telemetry, padding: 16 bytes.
Two uniform sets A→B and B→A bindings; select one per lifecycle pass.
# Select the current direction, record the pass, then flip.
var lifecycle_set := set_a_to_b if front_is_a else set_b_to_a
front_is_a = not front_is_a

05 Failure map

Symptom Likely cause
Duplicate or missing survivors In-place compaction or a non-atomic slot counter.
Occasional stale flags/counts Missing compute barrier between dependent dispatches.
Active count exceeds capacity Published next_count without clamping.
Writes corrupt another buffer Bounds test happened after the destination write.
Different order every run Expected for atomic, unstable compaction.
Removal hits the wrong body later A compacted slot index was mistaken for stable identity.
All records vanish Reset and compact were not separated by a barrier, or the source/destination set is reversed.