perf(device): pop retired deletions off the front in O(ready) (#116)
ReclaimDeletions ran an O(n) walk + survivor compaction over the whole deletion queue every frame. frameCounter is monotonic, so retireAfter is non-decreasing down the queue and the entries ready to reclaim are always a contiguous prefix. Switch deletionQueue from std::vector to std::deque and pop the ready prefix off the front, stopping at the first entry still in flight — O(ready) instead of O(queue). Resolves #116 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9d9f9d9d2c
commit
2460a66296
2 changed files with 20 additions and 15 deletions
|
|
@ -1010,21 +1010,22 @@ void Device::EnqueueDeletion(VkBuffer buffer, VkDeviceMemory memory) {
|
|||
}
|
||||
|
||||
void Device::ReclaimDeletions() {
|
||||
// Free every entry whose retire frame has been reached, compacting the
|
||||
// survivors down in place. An entry tagged at frame F retires at
|
||||
// F + framesInFlight: by the time the CPU has begun that many later
|
||||
// frames (each gated by a per-image fence wait), single-queue submission
|
||||
// order guarantees all GPU work from frame F is complete.
|
||||
std::size_t kept = 0;
|
||||
for (PendingDeletion& entry : deletionQueue) {
|
||||
if (entry.retireAfter + framesInFlight <= frameCounter) {
|
||||
// Free every entry whose retire frame has been reached. An entry tagged at
|
||||
// frame F retires at F + framesInFlight: by the time the CPU has begun that
|
||||
// many later frames (each gated by a per-image fence wait), single-queue
|
||||
// submission order guarantees all GPU work from frame F is complete.
|
||||
//
|
||||
// frameCounter is monotonic, so retireAfter is non-decreasing down the
|
||||
// queue and the ready entries are a contiguous prefix: pop them off the
|
||||
// front and stop at the first one still in flight (everything behind it is
|
||||
// newer, hence also in flight). O(ready), not O(queue).
|
||||
while (!deletionQueue.empty() &&
|
||||
deletionQueue.front().retireAfter + framesInFlight <= frameCounter) {
|
||||
const PendingDeletion& entry = deletionQueue.front();
|
||||
vkDestroyBuffer(device, entry.buffer, nullptr);
|
||||
vkFreeMemory(device, entry.memory, nullptr);
|
||||
} else {
|
||||
deletionQueue[kept++] = entry;
|
||||
deletionQueue.pop_front();
|
||||
}
|
||||
}
|
||||
deletionQueue.resize(kept);
|
||||
}
|
||||
|
||||
void Device::DrainDeletions() {
|
||||
|
|
|
|||
|
|
@ -320,7 +320,11 @@ export namespace Crafter {
|
|||
// Zero until set, which only makes ReclaimDeletions more eager — and it
|
||||
// is never called before a Window (which sets this) starts rendering.
|
||||
inline static std::uint8_t framesInFlight = 0;
|
||||
inline static std::vector<PendingDeletion> deletionQueue;
|
||||
// A deque, not a vector: frameCounter is monotonic, so retireAfter is
|
||||
// non-decreasing down the queue and the entries ready to reclaim are
|
||||
// always a prefix. ReclaimDeletions pops that prefix off the front in
|
||||
// O(ready) instead of walking and compacting the whole queue.
|
||||
inline static std::deque<PendingDeletion> deletionQueue;
|
||||
// Tag {buffer, memory} for deletion after framesInFlight more frames.
|
||||
// A VK_NULL_HANDLE buffer is ignored (nothing to free).
|
||||
static void EnqueueDeletion(VkBuffer buffer, VkDeviceMemory memory);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue