perf(agc): release a heap mirror once a host import serves its range
An address-based build maps every readable registered range: through a host import when one can be made, else through a heap mirror, a host-visible copy of the range. A range mirrored while the import budget was full kept its mirror after a later pass imported it, until the guest freed the range. The pass then read the range through the import, and the mirror only held host-visible memory and a second copy of the bytes. The pass now releases the heap mirror of every range it serves through an import. Recorded work that bound the mirror keeps it until it completes. If the import goes away while the range stays registered, the next pass makes a new mirror from the guest bytes. The [buffers] image mirrors profile line counts the released mirrors.
This commit is contained in:
parent
d70b899894
commit
3f00dc7841
|
|
@ -536,6 +536,8 @@ struct ImageMirrors {
|
|||
std::uint64_t builds = 0;
|
||||
std::uint64_t heapRefills = 0;
|
||||
std::uint64_t heapUnwatched = 0;
|
||||
std::uint64_t heapImported = 0;
|
||||
std::uint64_t heapImportedBytes = 0;
|
||||
std::uint64_t subranges = 0;
|
||||
std::uint64_t rebuilds = 0;
|
||||
std::uint64_t refreshes = 0;
|
||||
|
|
@ -905,6 +907,23 @@ void sweepMirrors() {
|
|||
}
|
||||
}
|
||||
|
||||
void releaseImportedMirrors(const Context& context, const std::vector<std::uint64_t>& imported) {
|
||||
if (imported.empty()) return;
|
||||
std::vector<std::shared_ptr<ImageMirror>> released;
|
||||
auto& state = Mirrors();
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (state.device != context.device || state.entries.empty()) return;
|
||||
for (const auto base : imported) {
|
||||
const auto found = state.entries.find(base);
|
||||
if (found == state.entries.end() || !found->second->heap) continue;
|
||||
state.heapBytes -= found->second->bytes;
|
||||
++state.heapImported;
|
||||
state.heapImportedBytes += found->second->bytes;
|
||||
released.push_back(std::move(found->second));
|
||||
state.entries.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
void reportMirrors() {
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
if (!profile) return;
|
||||
|
|
@ -927,7 +946,7 @@ void reportMirrors() {
|
|||
}
|
||||
heapBytes = state.heapBytes;
|
||||
}
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[buffers] image mirrors: %zu ranges (%.1f MiB, %zu writable, %zu heap %.1f MiB), %llu address-based builds served, %llu descriptor sub-ranges bound, %llu rebuilds, %llu refreshes: %llu blocks compared, %llu copied, %llu refresh syncs; heap refills %llu, unwatched heaps %llu\n", count, bytes / 1048576.0, writable, heaps, heapBytes / 1048576.0, static_cast<unsigned long long>(state.builds), static_cast<unsigned long long>(state.subranges), static_cast<unsigned long long>(state.rebuilds), static_cast<unsigned long long>(state.refreshes), static_cast<unsigned long long>(state.blocksCompared), static_cast<unsigned long long>(state.blocksCopied), static_cast<unsigned long long>(state.syncs), static_cast<unsigned long long>(state.heapRefills), static_cast<unsigned long long>(state.heapUnwatched));
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[buffers] image mirrors: %zu ranges (%.1f MiB, %zu writable, %zu heap %.1f MiB), %llu address-based builds served, %llu descriptor sub-ranges bound, %llu rebuilds, %llu refreshes: %llu blocks compared, %llu copied, %llu refresh syncs; heap refills %llu, unwatched heaps %llu, %llu heap mirrors released for imports (%.1f MiB)\n", count, bytes / 1048576.0, writable, heaps, heapBytes / 1048576.0, static_cast<unsigned long long>(state.builds), static_cast<unsigned long long>(state.subranges), static_cast<unsigned long long>(state.rebuilds), static_cast<unsigned long long>(state.refreshes), static_cast<unsigned long long>(state.blocksCompared), static_cast<unsigned long long>(state.blocksCopied), static_cast<unsigned long long>(state.syncs), static_cast<unsigned long long>(state.heapRefills), static_cast<unsigned long long>(state.heapUnwatched), static_cast<unsigned long long>(state.heapImported), state.heapImportedBytes / 1048576.0);
|
||||
}
|
||||
|
||||
// Deferred lease release. The lease an address-based build takes (AcquireRegistered) is dropped by its
|
||||
|
|
@ -1450,6 +1469,7 @@ void GuestBufferMemory::AcquireRegistered() {
|
|||
// must not happen under it.
|
||||
regions.reserve(lease.size());
|
||||
std::vector<AddressCopy> copies;
|
||||
std::vector<std::uint64_t> imported;
|
||||
for (const auto& range : lease) {
|
||||
if (!range->readable) continue;
|
||||
validate(range->address, range->bytes);
|
||||
|
|
@ -1466,6 +1486,7 @@ void GuestBufferMemory::AcquireRegistered() {
|
|||
// Reused by Upload while no import was dropped since (see importsEpoch).
|
||||
region.direct = entry;
|
||||
regions.push_back(std::move(region));
|
||||
imported.push_back(range->address);
|
||||
continue;
|
||||
}
|
||||
if (mirrorsEnabled()) {
|
||||
|
|
@ -1488,6 +1509,7 @@ void GuestBufferMemory::AcquireRegistered() {
|
|||
const char* reason = !mirrorsEnabled() ? "mirrors disabled by APS5_NO_LEASE_MIRROR" : copied.sparse ? "unreadable pages" : !range->releasable ? "image mirror refused" : "outside the write-watched arena";
|
||||
copies.push_back({copied.begin, copied.end, committed, reason});
|
||||
}
|
||||
releaseImportedMirrors(context, imported);
|
||||
regionsSorted = sortedLookup;
|
||||
static const std::uint64_t copyLimit = [] {
|
||||
const char* value = std::getenv("APS5_ADDRESS_COPY_MAX_MIB");
|
||||
|
|
|
|||
|
|
@ -36,6 +36,61 @@ ShaderRecompiler::DescriptorBinding binding(Role role, std::uint32_t slot) {
|
|||
return {ShaderRecompiler::DescriptorKind::StorageBuffer, role, 0, slot, 1, {}, false};
|
||||
}
|
||||
|
||||
void importedHeapMirrorTests(const Context& context, const BdaTestAccess& access) {
|
||||
if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) {
|
||||
std::cout << "guest arena unavailable or not write-watched: heap mirrors of imported ranges not tested\n";
|
||||
return;
|
||||
}
|
||||
constexpr std::size_t bytes = 1u << 20u;
|
||||
void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, bytes);
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
|
||||
#endif
|
||||
std::memset(block, 0x11, bytes);
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(block);
|
||||
auto importing = context;
|
||||
importing.hostImportAlignment = bytes;
|
||||
const auto registry = [&](bool add) {
|
||||
auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix();
|
||||
if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, block, bytes, true, false);
|
||||
else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block);
|
||||
GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation);
|
||||
};
|
||||
const auto build = [&](const Context& with) {
|
||||
GuestAllocations::GuestAllocationsEnd_nid_postfix(GuestAllocations::GuestAllocationsBegin_nid_postfix());
|
||||
GuestBufferMemory leased(with);
|
||||
leased.AcquireRegistered();
|
||||
leased.Upload(true);
|
||||
const auto ranges = leased.AddressRanges();
|
||||
const auto found = std::find_if(ranges.begin(), ranges.end(), [&](const auto& range) { return range.begin == address && range.end == address + bytes; });
|
||||
const auto device = found != ranges.end() ? found->deviceAddress : 0;
|
||||
leased.WriteBack();
|
||||
return device;
|
||||
};
|
||||
const auto before = MirrorCounters();
|
||||
registry(true);
|
||||
Require(build(context) != 0, "the heap range is missing from the BDA table of a build without host imports");
|
||||
const auto mirrored = MirrorCounters();
|
||||
Require(mirrored.heapMirrors == before.heapMirrors + 1 && mirrored.heapBytes == before.heapBytes + bytes, "a range built without host imports was not heap mirrored");
|
||||
Require(build(importing) != 0 && HostImportCovers(importing, address, bytes), "the heap mirrored range was not imported once imports were available");
|
||||
const auto imported = MirrorCounters();
|
||||
Require(imported.heapMirrors == before.heapMirrors && imported.heapBytes == before.heapBytes, "a heap mirror outlived the host import that serves its range");
|
||||
Require(build(importing) != 0 && MirrorCounters().heapMirrors == before.heapMirrors && MirrorCounters().rebuilds == imported.rebuilds, "an imported range was heap mirrored again");
|
||||
static_cast<std::uint8_t*>(block)[bytes / 2] = 0x22;
|
||||
const auto device = build(context);
|
||||
const auto again = MirrorCounters();
|
||||
Require(device != 0 && again.heapMirrors == before.heapMirrors + 1 && again.heapBytes == before.heapBytes + bytes && again.rebuilds == imported.rebuilds + 1, "a range no import serves was not heap mirrored again");
|
||||
Require(access.addressBytes(device)[bytes / 2] == std::byte{0x22} && access.addressBytes(device)[bytes / 2 + 1] == std::byte{0x11}, "a heap mirror made after its import missed the guest bytes");
|
||||
registry(false);
|
||||
Require(HostImportFor(importing, address, bytes) == nullptr && !HostImportCovers(importing, address, bytes), "the import outlived its range");
|
||||
Require(build(context) == 0, "the unregistered heap range is still in the BDA table");
|
||||
Require(MirrorCounters().heapMirrors == before.heapMirrors && MirrorCounters().heapBytes == before.heapBytes, "the heap mirror outlived its range");
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaReset_nid_postfix(block, bytes);
|
||||
#endif
|
||||
GuestArena::GuestArenaRelease_nid_postfix(block, bytes);
|
||||
}
|
||||
|
||||
void heapMirrorTests(const Context& context, const BdaTestAccess& access) {
|
||||
if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) {
|
||||
std::cout << "guest arena unavailable or not write-watched: heap mirrors not tested\n";
|
||||
|
|
@ -386,6 +441,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
|
|||
if (dropped.enabled) Require(dropped.waiterDrops == after.waiterDrops + 1 && LeaseCounters().cacheDrops == dropped.waiterDrops, "the free did not drop the cached address space");
|
||||
}
|
||||
heapMirrorTests(context, access);
|
||||
importedHeapMirrorTests(context, access);
|
||||
Require(AddressCopyOverflow({{0x1000, 0x3000, 0x2000, "uncommitted pages"}}, 0x2000).empty(), "copies within the limit were refused");
|
||||
const auto copies = AddressCopyOverflow({{0x1000, 0x2000, 0x1000, "not mirrored"}, {0x10000, 0x30000, 0x18000, "uncommitted pages"}}, 0x2000);
|
||||
Require(!copies.empty() && copies.find("0x10000+0x20000 (0.1 MiB committed, uncommitted pages)") < copies.find("0x1000+0x1000"), "the copy limit does not name the largest copy first");
|
||||
|
|
|
|||
|
|
@ -1300,6 +1300,12 @@ VKAPI_ATTR void VKAPI_CALL mockCmdBindDescriptorSets(VkCommandBuffer, VkPipeline
|
|||
mock.boundSets = count;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL mockGetMemoryHostPointerProperties(VkDevice, VkExternalMemoryHandleTypeFlagBits type, const void* pointer, VkMemoryHostPointerPropertiesEXT* properties) {
|
||||
Require(type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT && pointer != nullptr, "invalid host pointer import query");
|
||||
properties->memoryTypeBits = 1;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkDeviceAddress VKAPI_CALL mockGetBufferDeviceAddress(VkDevice, const VkBufferDeviceAddressInfo* info) {
|
||||
Require(mock.bufferMemory.contains(info->buffer), "BDA buffer was not bound");
|
||||
Require((mock.bufferUsage.at(info->buffer) & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0, "BDA buffer usage is missing");
|
||||
|
|
@ -1369,6 +1375,7 @@ VKAPI_ATTR void VKAPI_CALL mockCmdUpdateBuffer(VkCommandBuffer, VkBuffer buffer,
|
|||
PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) {
|
||||
static const std::map<std::string_view, PFN_vkVoidFunction> table{
|
||||
{"vkGetBufferDeviceAddressKHR", reinterpret_cast<PFN_vkVoidFunction>(mockGetBufferDeviceAddress)},
|
||||
{"vkGetMemoryHostPointerPropertiesEXT", reinterpret_cast<PFN_vkVoidFunction>(mockGetMemoryHostPointerProperties)},
|
||||
{"vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(mockCreateBuffer)},
|
||||
{"vkGetBufferMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(mockGetBufferMemoryRequirements)},
|
||||
{"vkAllocateMemory", reinterpret_cast<PFN_vkVoidFunction>(mockAllocateMemory)},
|
||||
|
|
|
|||
Loading…
Reference in New Issue