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:
parent
cc9266edaa
commit
c978e62ec8
@ -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.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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: {
|
||||||
|
Loading…
Reference in New Issue
Block a user