remove double locking. small perf boost

This commit is contained in:
nmzik
2026-08-06 23:23:28 +02:00
parent e9f5da27f8
commit 0cbae39e41
11 changed files with 97 additions and 298 deletions
-120
View File
@@ -1,120 +0,0 @@
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#undef min
#undef max
#endif
namespace {
using Libs::Graphics::ResourceMutex;
void Check(bool value, const char* text) {
if (!value) {
std::fprintf(stderr, "ResourceMutexTests: failed: %s\n", text);
std::abort();
}
}
void YieldMany() {
for (uint32_t i = 0; i < 4096; i++) {
std::this_thread::yield();
}
}
void TestOwnership() {
ResourceMutex mutex;
Check(!mutex.IsOwnedByCurrentThread(), "fresh mutex reported an owner");
{
std::lock_guard lock(mutex);
Check(mutex.IsOwnedByCurrentThread(), "owner tracking was not installed");
}
Check(!mutex.IsOwnedByCurrentThread(), "owner tracking survived unlock");
}
void TestSerializesTransactions() {
ResourceMutex mutex;
std::unique_lock owner(mutex);
std::atomic_bool contender_started {false};
std::atomic_bool contender_entered {false};
std::thread contender([&] {
contender_started.store(true, std::memory_order_release);
std::lock_guard lock(mutex);
contender_entered.store(true, std::memory_order_release);
});
while (!contender_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
YieldMany();
Check(!contender_entered.load(std::memory_order_acquire),
"contender entered an active resource transaction");
owner.unlock();
contender.join();
Check(contender_entered.load(std::memory_order_acquire),
"contender did not resume after resource transaction");
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
[[noreturn]] void RunDeathCase() {
ResourceMutex mutex;
std::lock_guard first(mutex);
std::lock_guard second(mutex);
std::_Exit(0x7f);
}
void CheckDeathCase() {
char path[MAX_PATH] {};
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed");
std::string command = std::string("\"") + path + "\" --death";
std::vector<char> mutable_command(command.begin(), command.end());
mutable_command.push_back('\0');
STARTUPINFOA startup {sizeof(startup)};
PROCESS_INFORMATION process {};
Check(CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
nullptr, nullptr, &startup, &process) != 0,
"CreateProcess failed");
const auto wait = WaitForSingleObject(process.hProcess, 10000);
if (wait != WAIT_OBJECT_0) {
TerminateProcess(process.hProcess, 0x7e);
}
Check(wait == WAIT_OBJECT_0, "ResourceMutex death test timed out");
DWORD exit_code = 0;
Check(GetExitCodeProcess(process.hProcess, &exit_code) != 0 && exit_code == 321,
"ResourceMutex death path used the wrong exit");
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
}
#endif
} // namespace
int main(int argc, char** argv) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (argc == 2 && std::strcmp(argv[1], "--death") == 0) {
RunDeathCase();
}
#else
(void)argc;
(void)argv;
#endif
TestOwnership();
TestSerializesTransactions();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
CheckDeathCase();
#endif
std::puts("ResourceMutexTests: all cases passed");
return 0;
}
+40 -5
View File
@@ -16,7 +16,6 @@
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
@@ -71,6 +70,7 @@
#include <initializer_list>
#include <limits>
#include <memory>
#include <mutex>
#include <numeric>
#include <optional>
#include <semaphore>
@@ -169,6 +169,10 @@ struct TextureCacheTestAccess {
static_assert(TextureCache::ImagePageTable::kAddressSpaceBits == 40);
static_assert(TextureCache::ImagePageTable::kFirstLevelBits == 10);
static std::unique_lock<TrackingSpinLock> Lock(TextureCache& cache) {
return std::unique_lock(cache.m_lock);
}
static void ConfigureGarbageCollection(TextureCache& cache, std::span<const ImageId> oldest,
uint64_t tick, uint64_t pressure) {
cache.m_trigger_gc_memory = 0;
@@ -261,13 +265,11 @@ struct TextureCacheTestAccess {
}
static ImageId InsertImage(TextureCache& cache, const ImageInfo& info) {
std::lock_guard transaction(cache.m_resource_mutex);
std::lock_guard lock(cache.m_lock);
return cache.InsertImage(info);
}
static void DeleteImage(TextureCache& cache, ImageId id) {
std::lock_guard transaction(cache.m_resource_mutex);
std::lock_guard lock(cache.m_lock);
cache.DeleteImage(id);
}
@@ -3021,8 +3023,41 @@ public:
"mip, rejected a fitting mip "
"prefix, or transferred ownership");
auto mip_formatted = resources.GetBufferCache().ObtainBuffer(
command, mip_desc.info.data.address, mip_desc.info.data.size, false, true, true);
auto& buffer_cache = resources.GetBufferCache();
Require(name, "formatted lock-handoff fixture",
!buffer_cache.HasPageOverlap(mip_desc.info.data.address,
mip_desc.info.data.size),
"formatted image already had a cached Buffer owner");
BufferBinding mip_formatted;
std::binary_semaphore obtain_entered {0};
std::binary_semaphore obtain_complete {0};
std::binary_semaphore buffer_visible {0};
auto texture_lock = TextureCacheTestAccess::Lock(texture_cache);
gpu.SendCommand([&] {
obtain_entered.release();
mip_formatted = buffer_cache.ObtainBuffer(command, mip_desc.info.data.address,
mip_desc.info.data.size, false, true, true);
obtain_complete.release();
});
obtain_entered.acquire();
std::jthread buffer_probe([&] {
while (!buffer_cache.HasPageOverlap(mip_desc.info.data.address,
mip_desc.info.data.size)) {
std::this_thread::yield();
}
buffer_visible.release();
});
const bool buffer_released_before_texture =
buffer_visible.try_acquire_for(std::chrono::seconds(2));
texture_lock.unlock();
if (!buffer_released_before_texture) {
buffer_visible.acquire();
}
obtain_complete.acquire();
buffer_probe.join();
Require(name, "formatted cache lock handoff", buffer_released_before_texture,
"formatted Buffer acquisition retained the BufferCache lock while waiting for "
"TextureCache");
Require(name, "formatted Buffer path",
mip_formatted.owner != nullptr && mip_formatted.buffer != nullptr &&
texture_cache.GetImage(mip_image).IsGpuModified(),