1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2024-12-12 13:08:50 +01:00

[dxvk] Implement better priority system for background shader compiles

Reduces the number of workers that perform background optimization,
which may reduce the performance impact when encountering a large
number of new pipelines at once.
This commit is contained in:
Philip Rebohle 2023-01-13 14:20:27 +01:00
parent cc9266edaa
commit c978e62ec8
4 changed files with 103 additions and 118 deletions

View File

@ -938,7 +938,7 @@ namespace dxvk {
// If necessary, compile an optimized pipeline variant // If necessary, compile an optimized pipeline variant
if (!instance->fastHandle.load()) if (!instance->fastHandle.load())
m_workers->compileGraphicsPipeline(this, state); m_workers->compileGraphicsPipeline(this, state, DxvkPipelinePriority::Low);
// Only store pipelines in the state cache that cannot benefit // Only store pipelines in the state cache that cannot benefit
// from pipeline libraries, or if that feature is disabled. // from pipeline libraries, or if that feature is disabled.

View File

@ -21,40 +21,28 @@ namespace dxvk {
void DxvkPipelineWorkers::compilePipelineLibrary( void DxvkPipelineWorkers::compilePipelineLibrary(
DxvkShaderPipelineLibrary* library, DxvkShaderPipelineLibrary* library,
DxvkPipelinePriority priority) { DxvkPipelinePriority priority) {
std::unique_lock lock(m_queueLock); std::unique_lock lock(m_lock);
this->startWorkers(); this->startWorkers();
m_pendingTasks += 1; m_pendingTasks += 1;
PipelineLibraryEntry e = { }; m_buckets[uint32_t(priority)].queue.emplace(library);
e.pipelineLibrary = library; notifyWorkers(priority);
if (priority == DxvkPipelinePriority::High) {
m_queuedLibrariesPrioritized.push(e);
m_queueCondPrioritized.notify_one();
} else {
m_queuedLibraries.push(e);
}
m_queueCond.notify_one();
} }
void DxvkPipelineWorkers::compileGraphicsPipeline( void DxvkPipelineWorkers::compileGraphicsPipeline(
DxvkGraphicsPipeline* pipeline, DxvkGraphicsPipeline* pipeline,
const DxvkGraphicsPipelineStateInfo& state) { const DxvkGraphicsPipelineStateInfo& state,
std::unique_lock lock(m_queueLock); DxvkPipelinePriority priority) {
std::unique_lock lock(m_lock);
this->startWorkers(); this->startWorkers();
pipeline->acquirePipeline(); pipeline->acquirePipeline();
m_pendingTasks += 1; m_pendingTasks += 1;
PipelineEntry e = { }; m_buckets[uint32_t(priority)].queue.emplace(pipeline, state);
e.graphicsPipeline = pipeline; notifyWorkers(priority);
e.graphicsState = state;
m_queuedPipelines.push(e);
m_queueCond.notify_one();
} }
@ -64,14 +52,15 @@ namespace dxvk {
void DxvkPipelineWorkers::stopWorkers() { void DxvkPipelineWorkers::stopWorkers() {
{ std::unique_lock lock(m_queueLock); { std::unique_lock lock(m_lock);
if (!m_workersRunning) if (!m_workersRunning)
return; return;
m_workersRunning = false; m_workersRunning = false;
m_queueCond.notify_all();
m_queueCondPrioritized.notify_all(); for (uint32_t i = 0; i < m_buckets.size(); i++)
m_buckets[i].cond.notify_all();
} }
for (auto& worker : m_workers) for (auto& worker : m_workers)
@ -81,8 +70,23 @@ namespace dxvk {
} }
void DxvkPipelineWorkers::notifyWorkers(DxvkPipelinePriority priority) {
uint32_t index = uint32_t(priority);
// If any workers are idle in a suitable set, notify the corresponding
// condition variable. If all workers are busy anyway, we know that the
// job is going to be picked up at some point anyway.
for (uint32_t i = index; i < m_buckets.size(); i++) {
if (m_buckets[i].idleWorkers) {
m_buckets[i].cond.notify_one();
break;
}
}
}
void DxvkPipelineWorkers::startWorkers() { void DxvkPipelineWorkers::startWorkers() {
if (!m_workersRunning) { if (!std::exchange(m_workersRunning, true)) {
// Use all available cores by default // Use all available cores by default
uint32_t workerCount = dxvk::thread::hardware_concurrency(); uint32_t workerCount = dxvk::thread::hardware_concurrency();
@ -98,102 +102,74 @@ namespace dxvk {
// Number of workers that can process pipeline pipelines with normal // Number of workers that can process pipeline pipelines with normal
// priority. Any other workers can only build high-priority pipelines. // priority. Any other workers can only build high-priority pipelines.
uint32_t npWorkerCount = m_device->canUseGraphicsPipelineLibrary() uint32_t npWorkerCount = std::max(((workerCount - 1) * 5) / 7, 1u);
? std::max(((workerCount - 1) * 5) / 7, 1u) uint32_t lpWorkerCount = std::max(((workerCount - 1) * 2) / 7, 1u);
: workerCount;
uint32_t hpWorkerCount = workerCount - npWorkerCount;
Logger::info(str::format("DXVK: Using ", npWorkerCount, " + ", hpWorkerCount, " compiler threads")); m_workers.reserve(workerCount);
m_workers.resize(npWorkerCount + hpWorkerCount);
// Set worker flag so that they don't exit immediately for (size_t i = 0; i < workerCount; i++) {
m_workersRunning = true; DxvkPipelinePriority priority = DxvkPipelinePriority::Normal;
for (size_t i = 0; i < m_workers.size(); i++) { if (m_device->canUseGraphicsPipelineLibrary()) {
m_workers[i] = i >= npWorkerCount if (i >= npWorkerCount)
? dxvk::thread([this] { runWorkerPrioritized(); }) priority = DxvkPipelinePriority::High;
: dxvk::thread([this] { runWorker(); }); else if (i < lpWorkerCount)
m_workers[i].set_priority(ThreadPriority::Lowest); priority = DxvkPipelinePriority::Low;
}
m_workers.emplace_back([this, priority] {
runWorker(priority);
});
} }
Logger::info(str::format("DXVK: Using ", workerCount, " compiler threads"));
} }
} }
void DxvkPipelineWorkers::runWorker() { void DxvkPipelineWorkers::runWorker(DxvkPipelinePriority maxPriority) {
env::setThreadName("dxvk-shader"); static const std::array<char, 3> suffixes = { 'h', 'n', 'l' };
const uint32_t maxPriorityIndex = uint32_t(maxPriority);
env::setThreadName(str::format("dxvk-shader-", suffixes.at(maxPriorityIndex)));
while (true) { while (true) {
std::optional<PipelineEntry> p; PipelineEntry entry;
std::optional<PipelineLibraryEntry> l;
{ std::unique_lock lock(m_queueLock); { std::unique_lock lock(m_lock);
auto& bucket = m_buckets[maxPriorityIndex];
m_queueCond.wait(lock, [this] { bucket.idleWorkers += 1;
return !m_workersRunning bucket.cond.wait(lock, [this, maxPriorityIndex, &entry] {
|| !m_queuedLibrariesPrioritized.empty() // Attempt to fetch a work item from the
|| !m_queuedLibraries.empty() // highest-priority queue that is not empty
|| !m_queuedPipelines.empty(); for (uint32_t i = 0; i <= maxPriorityIndex; i++) {
if (!m_buckets[i].queue.empty()) {
entry = m_buckets[i].queue.front();
m_buckets[i].queue.pop();
return true;
}
}
return !m_workersRunning;
}); });
if (!m_workersRunning) { bucket.idleWorkers -= 1;
// Skip pending work, exiting early is
// more important in this case.
break;
} else if (!m_queuedLibrariesPrioritized.empty()) {
l = m_queuedLibrariesPrioritized.front();
m_queuedLibrariesPrioritized.pop();
} else if (!m_queuedLibraries.empty()) {
l = m_queuedLibraries.front();
m_queuedLibraries.pop();
} else if (!m_queuedPipelines.empty()) {
p = m_queuedPipelines.front();
m_queuedPipelines.pop();
}
}
if (l) {
if (l->pipelineLibrary)
l->pipelineLibrary->compilePipeline();
m_pendingTasks -= 1;
}
if (p) {
if (p->graphicsPipeline) {
p->graphicsPipeline->compilePipeline(p->graphicsState);
p->graphicsPipeline->releasePipeline();
}
m_pendingTasks -= 1;
}
}
}
void DxvkPipelineWorkers::runWorkerPrioritized() {
env::setThreadName("dxvk-shader-p");
while (true) {
PipelineLibraryEntry l = { };
{ std::unique_lock lock(m_queueLock);
m_queueCondPrioritized.wait(lock, [this] {
return !m_workersRunning
|| !m_queuedLibrariesPrioritized.empty();
});
// Skip pending work, exiting early is
// more important in this case.
if (!m_workersRunning) if (!m_workersRunning)
break; break;
l = m_queuedLibrariesPrioritized.front();
m_queuedLibrariesPrioritized.pop();
} }
if (l.pipelineLibrary) if (entry.pipelineLibrary) {
l.pipelineLibrary->compilePipeline(); entry.pipelineLibrary->compilePipeline();
m_pendingTasks -= 1;
m_pendingTasks -= 1; } else if (entry.graphicsPipeline) {
entry.graphicsPipeline->compilePipeline(entry.graphicsState);
entry.graphicsPipeline->releasePipeline();
m_pendingTasks -= 1;
}
} }
} }

View File

@ -38,8 +38,9 @@ namespace dxvk {
* \brief Pipeline priority * \brief Pipeline priority
*/ */
enum class DxvkPipelinePriority : uint32_t { enum class DxvkPipelinePriority : uint32_t {
Normal = 0, High = 0,
High = 1, Normal = 1,
Low = 2,
}; };
/** /**
@ -78,7 +79,8 @@ namespace dxvk {
*/ */
void compileGraphicsPipeline( void compileGraphicsPipeline(
DxvkGraphicsPipeline* pipeline, DxvkGraphicsPipeline* pipeline,
const DxvkGraphicsPipelineStateInfo& state); const DxvkGraphicsPipelineStateInfo& state,
DxvkPipelinePriority priority);
/** /**
* \brief Checks whether workers are busy * \brief Checks whether workers are busy
@ -97,34 +99,41 @@ namespace dxvk {
private: private:
struct PipelineEntry { struct PipelineEntry {
PipelineEntry()
: pipelineLibrary(nullptr), graphicsPipeline(nullptr) { }
PipelineEntry(DxvkShaderPipelineLibrary* l)
: pipelineLibrary(l), graphicsPipeline(nullptr) { }
PipelineEntry(DxvkGraphicsPipeline* p, const DxvkGraphicsPipelineStateInfo& s)
: pipelineLibrary(nullptr), graphicsPipeline(p), graphicsState(s) { }
DxvkShaderPipelineLibrary* pipelineLibrary;
DxvkGraphicsPipeline* graphicsPipeline; DxvkGraphicsPipeline* graphicsPipeline;
DxvkGraphicsPipelineStateInfo graphicsState; DxvkGraphicsPipelineStateInfo graphicsState;
}; };
struct PipelineLibraryEntry { struct PipelineBucket {
DxvkShaderPipelineLibrary* pipelineLibrary; dxvk::condition_variable cond;
std::queue<PipelineEntry> queue;
uint32_t idleWorkers = 0;
}; };
DxvkDevice* m_device; DxvkDevice* m_device;
std::atomic<uint64_t> m_pendingTasks = { 0ull }; std::atomic<uint64_t> m_pendingTasks = { 0ull };
dxvk::mutex m_queueLock; dxvk::mutex m_lock;
dxvk::condition_variable m_queueCond; std::array<PipelineBucket, 3> m_buckets;
dxvk::condition_variable m_queueCondPrioritized;
std::queue<PipelineLibraryEntry> m_queuedLibrariesPrioritized;
std::queue<PipelineLibraryEntry> m_queuedLibraries;
std::queue<PipelineEntry> m_queuedPipelines;
bool m_workersRunning = false; bool m_workersRunning = false;
std::vector<dxvk::thread> m_workers; std::vector<dxvk::thread> m_workers;
void notifyWorkers(DxvkPipelinePriority priority);
void startWorkers(); void startWorkers();
void runWorker(); void runWorker(DxvkPipelinePriority maxPriority);
void runWorkerPrioritized();
}; };

View File

@ -453,7 +453,7 @@ namespace dxvk {
if (!pipeline) if (!pipeline)
pipeline = m_pipeManager->createGraphicsPipeline(item.gp); pipeline = m_pipeManager->createGraphicsPipeline(item.gp);
m_pipeWorkers->compileGraphicsPipeline(pipeline, entry.gpState); m_pipeWorkers->compileGraphicsPipeline(pipeline, entry.gpState, DxvkPipelinePriority::Normal);
} break; } break;
case DxvkStateCacheEntryType::PipelineLibrary: { case DxvkStateCacheEntryType::PipelineLibrary: {