Merge pull request #8913 from Celegans12/perf/agc-pool-exact-size-hit

perf(agc): prefer a pooled buffer of the requested exact size
This commit is contained in:
Dean authored and GitHub committed 2026-10-27 18:18:50 +13:00
commit a0cf4c4d23
3 files changed
+37 -3

No files matched your search

@@ -184,6 +184,7 @@ private:
static constexpr std::size_t classLimit = std::size_t{1} << 20u;
static constexpr unsigned deviceClassBits = 3;
static constexpr std::size_t defaultSlots = 4096;
static constexpr std::size_t exactSizeScan = 64;
};
std::shared_ptr<BufferPool> GetBufferPool(const Context& context);
@@ -7,6 +7,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <limits>
namespace AgcDriver::Graphics {
@@ -411,9 +412,14 @@ std::optional<BufferAllocation> BufferPool::Take(std::size_t bytes, VkBufferUsag
++tier.misses;
return std::nullopt;
}
auto result = found->second.back().allocation;
found->second.pop_back();
if (found->second.empty()) tier.free.erase(found);
auto& slots = found->second;
const auto newest = std::prev(slots.end());
auto chosen = newest;
for (std::size_t scanned = 1; chosen->allocation.bufferBytes != bytes && scanned < exactSizeScan && chosen != slots.begin(); ++scanned) --chosen;
if (chosen->allocation.bufferBytes != bytes) chosen = newest;
auto result = chosen->allocation;
slots.erase(chosen);
if (slots.empty()) tier.free.erase(found);
--tier.slots;
tier.retainedBytes -= result.allocationBytes;
++tier.hits;
@@ -350,6 +350,32 @@ void LargerClass() {
Expect(MemoryOf(best.Handle()) != twelve && ClassOf(best.Handle()) == 5 * MiB, "a 4 MiB + 1 request did not take the smallest retained class that fits (5 MiB)");
}
void ExactSizeReuse() {
mock = MockDevice{};
{
auto context = mockContext();
VkBuffer first = VK_NULL_HANDLE;
VkBuffer second = VK_NULL_HANDLE;
{
auto a = std::make_unique<Buffer>(context, 3000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible);
auto b = std::make_unique<Buffer>(context, 4000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible);
first = a->Handle();
second = b->Handle();
a.reset();
b.reset();
}
const auto destroyed = mock.destroyedBuffers;
{
Buffer again(context, 3000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible);
Expect(again.Handle() == first && mock.destroyedBuffers == destroyed, "a pooled buffer of the requested exact size was passed over for a newer one that had to be rebuilt");
Expect(mock.sizes.at(again.Handle()) == 3000 && again.Bytes().size() == 3000, "a reused buffer does not have the requested size");
}
Buffer other(context, 4000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible);
Expect(other.Handle() == second && mock.sizes.at(other.Handle()) == 4000, "the other exact size was not kept for its own request");
}
Expect(mock.liveBytes == 0 && mock.liveBuffers.empty(), "exact-size reuse leaked Vulkan objects");
}
void KeptUntilFence() {
mock = MockDevice{};
auto context = mockContext();
@@ -1168,6 +1194,7 @@ int main(int argc, char** argv) {
Expect(mock.liveBytes == 0, "slab return test leaked memory");
SizeClassesAndDirections();
LargerClass();
ExactSizeReuse();
KeptUntilFence();
SmallDeviceRetention();
Budget();