Files

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;
}
}