mirror of
https://github.com/Awakenchan/LanternEngine.git
synced 2026-11-12 18:08:45 -10:00
332 lines
8.3 KiB
C++
332 lines
8.3 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
#include "bbport_copy.h"
|
|
#include "bbport_threads.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <cstdlib>
|
|
#include <deque>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "bbport_toggles.h"
|
|
#include "common/thread.h"
|
|
|
|
namespace BbCopy {
|
|
namespace {
|
|
|
|
thread_local bool in_copy_thread = false;
|
|
thread_local bool for_gpu_side = false;
|
|
std::atomic<bool (*)()> gpu_side_query{nullptr};
|
|
|
|
class Pool {
|
|
public:
|
|
Pool() {
|
|
unsigned count = std::clamp(BbThreads::Available() / 4, 1u, 4u);
|
|
if (const char* env = std::getenv("BB_COPY_THREADS")) {
|
|
count = static_cast<unsigned>(std::clamp(std::atoi(env), 0, 16));
|
|
}
|
|
epochs.emplace_back();
|
|
for (unsigned i = 0; i < count; ++i) {
|
|
threads.emplace_back([this, i] { Loop(i); });
|
|
}
|
|
}
|
|
~Pool() {
|
|
{
|
|
std::scoped_lock lk{mutex};
|
|
stop = true;
|
|
}
|
|
cv.notify_all();
|
|
for (auto& thread : threads) {
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
bool Enabled() const {
|
|
return !threads.empty();
|
|
}
|
|
|
|
void Async(std::function<void()> task) {
|
|
{
|
|
std::scoped_lock lk{mutex};
|
|
const u64 epoch = epoch_base + epochs.size() - 1;
|
|
++epochs.back().pending;
|
|
++pending;
|
|
async_tasks.push_back({std::move(task), epoch});
|
|
}
|
|
cv.notify_one();
|
|
}
|
|
|
|
void AfterCopies(std::function<void()> callback) {
|
|
{
|
|
std::unique_lock lk{mutex};
|
|
if (epochs.size() > 1 || epochs.back().pending != 0 || draining) {
|
|
epochs.back().callbacks.push_back(std::move(callback));
|
|
epochs.emplace_back();
|
|
return;
|
|
}
|
|
}
|
|
callback();
|
|
}
|
|
|
|
void WaitAll() {
|
|
while (true) {
|
|
Task task;
|
|
{
|
|
std::scoped_lock lk{mutex};
|
|
if (pending == 0 && epochs.size() == 1 && !draining) {
|
|
return;
|
|
}
|
|
if (!async_tasks.empty()) {
|
|
task = std::move(async_tasks.front());
|
|
async_tasks.pop_front();
|
|
}
|
|
}
|
|
if (task.run) {
|
|
RunAsync(task);
|
|
} else {
|
|
std::this_thread::yield();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HelpOne() {
|
|
Task task;
|
|
{
|
|
std::scoped_lock lk{mutex};
|
|
if (async_tasks.empty()) {
|
|
return false;
|
|
}
|
|
task = std::move(async_tasks.front());
|
|
async_tasks.pop_front();
|
|
}
|
|
RunAsync(task);
|
|
return true;
|
|
}
|
|
|
|
void Run(std::size_t count, const std::function<void(std::size_t)>& task, bool gpu_side) {
|
|
auto job = std::make_shared<Job>();
|
|
job->task = &task;
|
|
job->count = count;
|
|
job->gpu_side = gpu_side;
|
|
{
|
|
std::scoped_lock lk{mutex};
|
|
current = job;
|
|
++generation;
|
|
}
|
|
cv.notify_all();
|
|
Work(*job);
|
|
while (job->done.load(std::memory_order_acquire) < count) {
|
|
std::this_thread::yield();
|
|
}
|
|
std::scoped_lock lk{mutex};
|
|
if (current == job) {
|
|
current.reset();
|
|
}
|
|
}
|
|
|
|
private:
|
|
using u64 = unsigned long long;
|
|
struct Task {
|
|
std::function<void()> run;
|
|
u64 epoch{};
|
|
};
|
|
struct Epoch {
|
|
std::size_t pending = 0;
|
|
std::vector<std::function<void()>> callbacks;
|
|
};
|
|
struct Job {
|
|
const std::function<void(std::size_t)>* task{};
|
|
std::size_t count{};
|
|
bool gpu_side{};
|
|
std::atomic<std::size_t> next{0};
|
|
std::atomic<std::size_t> done{0};
|
|
};
|
|
|
|
void RunAsync(Task& task) {
|
|
const bool was = in_copy_thread;
|
|
in_copy_thread = true;
|
|
task.run();
|
|
in_copy_thread = was;
|
|
Complete(task.epoch);
|
|
}
|
|
|
|
void Complete(u64 epoch) {
|
|
std::unique_lock lk{mutex};
|
|
--epochs[epoch - epoch_base].pending;
|
|
--pending;
|
|
if (draining) {
|
|
return;
|
|
}
|
|
draining = true;
|
|
while (true) {
|
|
std::vector<std::function<void()>> ready;
|
|
while (epochs.size() > 1 && epochs.front().pending == 0) {
|
|
for (auto& callback : epochs.front().callbacks) {
|
|
ready.push_back(std::move(callback));
|
|
}
|
|
epochs.pop_front();
|
|
++epoch_base;
|
|
}
|
|
if (ready.empty()) {
|
|
break;
|
|
}
|
|
lk.unlock();
|
|
for (auto& callback : ready) {
|
|
callback();
|
|
}
|
|
lk.lock();
|
|
}
|
|
draining = false;
|
|
}
|
|
|
|
static void Work(Job& job) {
|
|
const bool was = in_copy_thread, was_gpu_side = for_gpu_side;
|
|
in_copy_thread = true;
|
|
for_gpu_side = job.gpu_side;
|
|
for (std::size_t i; (i = job.next.fetch_add(1, std::memory_order_relaxed)) < job.count;) {
|
|
(*job.task)(i);
|
|
job.done.fetch_add(1, std::memory_order_release);
|
|
}
|
|
in_copy_thread = was;
|
|
for_gpu_side = was_gpu_side;
|
|
}
|
|
|
|
void Loop(unsigned index) {
|
|
Common::SetCurrentThreadName(("bb:Copy" + std::to_string(index)).c_str());
|
|
BbThreads::MakeCritical( true);
|
|
u64 seen = 0;
|
|
std::unique_lock lk{mutex};
|
|
while (true) {
|
|
cv.wait(lk, [&] {
|
|
return stop || !async_tasks.empty() || (generation != seen && current);
|
|
});
|
|
if (stop) {
|
|
return;
|
|
}
|
|
if (!async_tasks.empty()) {
|
|
Task task = std::move(async_tasks.front());
|
|
async_tasks.pop_front();
|
|
lk.unlock();
|
|
RunAsync(task);
|
|
lk.lock();
|
|
continue;
|
|
}
|
|
seen = generation;
|
|
const auto job = current;
|
|
lk.unlock();
|
|
Work(*job);
|
|
lk.lock();
|
|
}
|
|
}
|
|
|
|
std::vector<std::thread> threads;
|
|
std::mutex mutex;
|
|
std::condition_variable cv;
|
|
std::shared_ptr<Job> current;
|
|
u64 generation = 0;
|
|
bool stop = false;
|
|
std::deque<Task> async_tasks;
|
|
std::deque<Epoch> epochs;
|
|
u64 epoch_base = 0;
|
|
std::size_t pending = 0;
|
|
bool draining = false;
|
|
};
|
|
|
|
Pool& GetPool() {
|
|
static Pool pool;
|
|
return pool;
|
|
}
|
|
|
|
struct Batch {
|
|
std::vector<Item> items;
|
|
unsigned long long bytes = 0;
|
|
};
|
|
thread_local Batch batch;
|
|
|
|
}
|
|
|
|
bool Enabled() {
|
|
return GetPool().Enabled() && !BbToggle::Disabled(BbToggle::ParallelCopies);
|
|
}
|
|
|
|
void Async(std::function<void()> task) {
|
|
if (!Enabled()) {
|
|
task();
|
|
return;
|
|
}
|
|
GetPool().Async(std::move(task));
|
|
}
|
|
|
|
void FlushBatch() {
|
|
if (batch.items.empty()) {
|
|
return;
|
|
}
|
|
auto items = std::make_shared<std::vector<Item>>(std::move(batch.items));
|
|
batch.items = {};
|
|
batch.items.reserve(256);
|
|
batch.bytes = 0;
|
|
Async([items] {
|
|
for (const auto& item : *items) {
|
|
item.run(item);
|
|
}
|
|
});
|
|
}
|
|
|
|
void QueueCopy(const Item& item) {
|
|
if (!Enabled()) {
|
|
item.run(item);
|
|
return;
|
|
}
|
|
batch.items.push_back(item);
|
|
batch.bytes += item.size;
|
|
if (batch.bytes >= 512 * 1024 || batch.items.size() >= 256) {
|
|
FlushBatch();
|
|
}
|
|
}
|
|
|
|
void AfterCopies(std::function<void()> callback) {
|
|
FlushBatch();
|
|
GetPool().AfterCopies(std::move(callback));
|
|
}
|
|
|
|
void WaitAsync() {
|
|
if (!batch.items.empty()) {
|
|
for (const auto& item : batch.items) {
|
|
item.run(item);
|
|
}
|
|
batch.items.clear();
|
|
batch.bytes = 0;
|
|
}
|
|
GetPool().WaitAll();
|
|
}
|
|
|
|
bool HelpOne() {
|
|
return GetPool().Enabled() && GetPool().HelpOne();
|
|
}
|
|
|
|
void ParallelFor(std::size_t count, const std::function<void(std::size_t)>& task) {
|
|
if (count <= 1 || in_copy_thread || !Enabled()) {
|
|
for (std::size_t i = 0; i < count; ++i) {
|
|
task(i);
|
|
}
|
|
return;
|
|
}
|
|
const auto query = gpu_side_query.load(std::memory_order_acquire);
|
|
GetPool().Run(count, task, query && query());
|
|
}
|
|
|
|
void SetGpuSideQuery(bool (*query)()) {
|
|
gpu_side_query.store(query, std::memory_order_release);
|
|
}
|
|
|
|
bool ForGpuSide() {
|
|
return for_gpu_side;
|
|
}
|
|
|
|
}
|