feat: simulation tasks can now be subdivided into separate workloads for separate threads

This commit is contained in:
Sara Gerretsen 2025-09-23 12:10:32 +02:00
parent e1bb3beeed
commit df31dea9c7

View file

@ -11,20 +11,31 @@
#include <set> #include <set>
#include <vector> #include <vector>
namespace simulation {
bool drawDebugInfo{ true };
static std::set<Cell> living{};
static std::vector<Cell> overpopulated{};
static std::vector<Cell> underpopulated{};
static std::vector<Cell> born{};
#ifdef __glibc_likely #ifdef __glibc_likely
#define likely(cond_) __glibc_likely(cond_) #define likely(cond_) __glibc_likely(cond_)
#else #else
#define likely(cond_) (cond_) #define likely(cond_) (cond_)
#endif #endif
#define SIM_MULTITHREADING 0
namespace simulation {
bool drawDebugInfo{ true };
struct ThreadWorkload {
size_t seg_idx{0}, seg_len{1};
std::mutex mtx{};
std::vector<Cell> changes{};
};
static std::set<Cell> living{};
static std::vector<std::shared_ptr<ThreadWorkload>> overpopulated{};
static std::vector<std::shared_ptr<ThreadWorkload>> underpopulated{};
static std::vector<std::shared_ptr<ThreadWorkload>> born{};
static uint64_t generationStartTime{ 0 };
static unsigned tasks{ 0 }; std::mutex tasksMutex{};
CellIterator::CellIterator(Cell begin, Cell end) CellIterator::CellIterator(Cell begin, Cell end)
: state{ begin } , begin{ begin }, end{ end } {} : state{ begin } , begin{ begin }, end{ end } {}
@ -56,16 +67,6 @@ bool CellIterator::at_end() const {
return this->state.y == this->end.y; return this->state.y == this->end.y;
} }
static std::vector<Cell> NeighborSet(Cell cell) {
std::vector<Cell> out{};
for (Cell const &c : CellRange{ { cell.x - 1, cell.y - 1 }, { cell.x + 2, cell.y + 2 } }) {
if (likely(c != cell)) {
out.push_back(c);
}
}
return out;
}
static size_t CountNeighbors(Cell const &cell) { static size_t CountNeighbors(Cell const &cell) {
size_t count{ 0 }; size_t count{ 0 };
for (Cell const &c : CellRange{ {cell.x - 1, cell.y - 1}, { cell.x + 2, cell.y + 2} }) { for (Cell const &c : CellRange{ {cell.x - 1, cell.y - 1}, { cell.x + 2, cell.y + 2} }) {
@ -76,100 +77,125 @@ static size_t CountNeighbors(Cell const &cell) {
return count; return count;
} }
static std::mutex underpopulatedMutex, overpopulatedMutex, bornMutex; static void TaskComplete() {
std::scoped_lock lock{ tasksMutex };
static void FindOverpopulated(size_t segment, size_t length) { if (--tasks == 0) {
overpopulatedMutex.lock(); SDL_Log("Generation Complete %lfs", (double)(SDL_GetTicksNS() - generationStartTime) * 0.0000000001);
std::set<Cell>::iterator begin{ living.begin() };
std::set<Cell>::iterator end{ living.begin() };
std::advance(begin, segment * length);
std::advance(end, segment * length + length);
std::copy_if(begin, end, std::back_inserter(overpopulated),
[&](Cell const &c) -> bool {
size_t const neighbors{ CountNeighbors(c) };
return neighbors > 3;
});
overpopulatedMutex.unlock();
}
static void FindUnderpopulated(size_t segment, size_t length) {
uint64_t ns{ SDL_GetTicksNS() };
underpopulatedMutex.lock();
std::set<Cell>::iterator begin{ living.begin() };
std::set<Cell>::iterator end{ living.begin() };
std::advance(begin, segment * length);
std::advance(end, segment * length + length);
std::copy_if(begin, end, std::back_inserter(underpopulated),
[&](Cell const &c) -> bool {
size_t const neighbors{ CountNeighbors(c) };
return neighbors < 2;
});
underpopulatedMutex.unlock();
}
static void FindBorn(size_t segment, size_t length) {
uint64_t ns{ SDL_GetTicksNS() };
bornMutex.lock();
std::set<Cell>::iterator itr{ living.begin() };
std::set<Cell>::iterator end{ living.begin() };
std::advance(itr, segment * length);
std::advance(end, segment * length + length);
for (;itr != end; ++itr) {
std::vector<Cell> neighbors{ NeighborSet(*itr) };
std::ranges::copy_if(neighbors, std::back_inserter(born),
[&](Cell const &c) -> bool {
size_t const neighbors{ CountNeighbors(c) };
return !living.contains(c) && neighbors == 3;
});
} }
bornMutex.unlock();
} }
#define MULTITHREADING 1 static void FindOverpopulated(std::shared_ptr<ThreadWorkload> wl) {
static uint64_t generationStartTime{ 0 }; std::scoped_lock lock{ wl->mtx };
static bool logGenerationTime{ false }; wl->changes.clear();
std::set<Cell>::iterator begin{ living.begin() };
std::set<Cell>::iterator end{ living.begin() };
std::advance(begin, wl->seg_idx * wl->seg_len);
std::advance(end, wl->seg_idx * wl->seg_len + wl->seg_len);
std::copy_if(begin, end, std::back_inserter(wl->changes),
[&](Cell const &c) -> bool {
return CountNeighbors(c) > 3;
});
TaskComplete();
}
static void FindUnderpopulated(std::shared_ptr<ThreadWorkload> wl) {
std::scoped_lock lock{ wl->mtx };
wl->changes.clear();
std::set<Cell>::iterator begin{ living.begin() };
std::set<Cell>::iterator end{ living.begin() };
std::advance(begin, wl->seg_idx * wl->seg_len);
std::advance(end, wl->seg_idx * wl->seg_len + wl->seg_len);
std::copy_if(begin, end, std::back_inserter(wl->changes),
[&](Cell const &c) -> bool {
return CountNeighbors(c) < 2;
});
TaskComplete();
}
static void FindBorn(std::shared_ptr<ThreadWorkload> wl) {
std::scoped_lock lock{ wl->mtx };
wl->changes.clear();
std::set<Cell>::iterator itr{ living.begin() };
std::advance(itr, wl->seg_idx * wl->seg_len);
std::for_each_n(itr, wl->seg_len, [&wl](Cell const &center){
CellRange range{{ center.x - 1, center.y - 1 }, {center.x + 2, center.y + 2 }};
std::copy_if(range.begin(), range.end(), std::back_inserter(wl->changes),
[&](Cell const &c) -> bool {
return !living.contains(c) && CountNeighbors(c) == 3;
});
});
TaskComplete();
}
static void PopulateChanges() { static void PopulateChanges() {
#if MULTITHREADING static bool first_run{ true };
constexpr size_t split{ 1 }; #if SIM_MULTITHREADING
constexpr size_t split{ 4 };
if (first_run) {
first_run = false;
born.resize(split);
underpopulated.resize(split);
overpopulated.resize(split);
}
SDL_Log("Multithreading ON"); SDL_Log("Multithreading ON");
logGenerationTime = true; { std::scoped_lock lock{ tasksMutex };
tasks = 3 * split; }
generationStartTime = SDL_GetTicksNS(); generationStartTime = SDL_GetTicksNS();
size_t const seg_length{ living.size() / split }; size_t const seg_length{ living.size() / split };
for (size_t i{ 0 }; i < split; ++i) { for (size_t i{ 0 }; i < split; ++i) {
threading::tasks.ScheduleTask(std::bind(FindOverpopulated, i, seg_length)); if (overpopulated[i] == nullptr)
threading::tasks.ScheduleTask(std::bind(FindUnderpopulated, i, seg_length)); overpopulated[i] = std::make_shared<ThreadWorkload>();
threading::tasks.ScheduleTask(std::bind(FindBorn, i, seg_length)); { std::scoped_lock lock{ overpopulated[i]->mtx };
overpopulated[i]->seg_idx = i; overpopulated[i]->seg_len = seg_length;
threading::tasks.ScheduleTask(std::bind(FindOverpopulated, overpopulated[i]));
}
if (underpopulated[i] == nullptr)
underpopulated[i] = std::make_shared<ThreadWorkload>();
{ std::scoped_lock lock{ underpopulated[i]->mtx };
underpopulated[i]->seg_idx = i; underpopulated[i]->seg_len = seg_length;
threading::tasks.ScheduleTask(std::bind(FindUnderpopulated, underpopulated[i]));
}
if (born[i] == nullptr)
born[i] = std::make_shared<ThreadWorkload>();
{ std::scoped_lock lock{ born[i]->mtx };
born[i]->seg_idx = i; born[i]->seg_len = seg_length;
threading::tasks.ScheduleTask(std::bind(FindBorn, born[i]));
}
} }
#else #else
if (first_run) {
first_run = false;
overpopulated.emplace_back(std::make_shared<ThreadWorkload>());
overpopulated[0]->seg_idx = 0;
overpopulated[0]->seg_len = living.size();
underpopulated.emplace_back(std::make_shared<ThreadWorkload>());
underpopulated[0]->seg_idx = 0;
underpopulated[0]->seg_len = living.size();
born.emplace_back(std::make_shared<ThreadWorkload>());
born[0]->seg_idx = 0;
born[0]->seg_len = living.size();
}
SDL_Log("Multithreading OFF"); SDL_Log("Multithreading OFF");
logGenerationTime = true; { std::scoped_lock lock{ tasksMutex };
tasks = 3; }
generationStartTime = SDL_GetTicksNS(); generationStartTime = SDL_GetTicksNS();
FindOverpopulated(0, living.size()); FindOverpopulated(overpopulated[0]);
FindUnderpopulated(0, living.size()); FindUnderpopulated(underpopulated[0]);
FindBorn(0, living.size()); FindBorn(born[0]);
#endif #endif
} }
void InitializeRandom(size_t livingChance, int64_t fillArea) { void InitializeRandom(size_t livingChance, int64_t fillArea) {
underpopulatedMutex.lock();
underpopulated.clear();
underpopulatedMutex.unlock();
overpopulatedMutex.lock();
overpopulated.clear();
overpopulatedMutex.unlock();
bornMutex.lock();
born.clear();
bornMutex.unlock();
living.clear(); living.clear();
Cell itr{ 0, 0 }; Cell itr{ 0, 0 };
@ -187,28 +213,24 @@ void InitializeRandom(size_t livingChance, int64_t fillArea) {
} }
void Step() { void Step() {
underpopulatedMutex.lock(); for (std::shared_ptr<ThreadWorkload> wl : underpopulated) {
overpopulatedMutex.lock(); if (wl == nullptr) continue;
bornMutex.lock(); std::scoped_lock l{ wl->mtx };
for (Cell const &cell : wl->changes)
for (Cell const &cell : underpopulated) { living.erase(cell);
living.erase(cell);
} }
underpopulated.clear(); for (std::shared_ptr<ThreadWorkload> wl : overpopulated) {
if (wl == nullptr) continue;
for (Cell const &cell : overpopulated) { std::scoped_lock l{ wl->mtx };
living.erase(cell); for (Cell const &cell : wl->changes)
living.erase(cell);
} }
overpopulated.clear(); for (std::shared_ptr<ThreadWorkload> wl : born) {
if (wl == nullptr) continue;
for (Cell const &cell : born) { std::scoped_lock l{ wl->mtx };
living.insert(cell); for (Cell const &cell : wl->changes)
living.insert(cell);
} }
born.clear();
underpopulatedMutex.unlock();
overpopulatedMutex.unlock();
bornMutex.unlock();
PopulateChanges(); PopulateChanges();
} }
@ -236,34 +258,32 @@ void Draw(SDL_Renderer *renderer, double cellSizePercent) {
if (!drawDebugInfo) { if (!drawDebugInfo) {
return; return;
} }
overpopulatedMutex.lock(); for (std::shared_ptr<ThreadWorkload> wl : overpopulated) {
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); std::scoped_lock l{ wl->mtx };
for (Cell const &cell : overpopulated) { SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
cellRect.x = (viewOffset.x + cell.x) * cellRect.w; for (Cell const &cell : wl->changes) {
cellRect.y = (viewOffset.y + cell.y) * cellRect.h; cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
SDL_RenderRect(renderer, &cellRect); cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
SDL_RenderRect(renderer, &cellRect);
}
} }
overpopulatedMutex.unlock(); for (std::shared_ptr<ThreadWorkload> wl : underpopulated) {
underpopulatedMutex.lock(); std::scoped_lock l{ wl->mtx };
SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255); SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
for (Cell const &cell : underpopulated) { for (Cell const &cell : wl->changes) {
cellRect.x = (viewOffset.x + cell.x) * cellRect.w; cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
cellRect.y = (viewOffset.y + cell.y) * cellRect.h; cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
SDL_RenderRect(renderer, &cellRect); SDL_RenderRect(renderer, &cellRect);
}
} }
underpopulatedMutex.unlock(); for (std::shared_ptr<ThreadWorkload> wl : born) {
bornMutex.lock(); std::scoped_lock l{ wl->mtx };
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
for (Cell const &cell : born) { for (Cell const &cell : wl->changes) {
cellRect.x = (viewOffset.x + cell.x) * cellRect.w; cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
cellRect.y = (viewOffset.y + cell.y) * cellRect.h; cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
SDL_RenderRect(renderer, &cellRect); SDL_RenderRect(renderer, &cellRect);
} }
bornMutex.unlock();
if (logGenerationTime) {
SDL_Log("End Generation %lf", double(SDL_GetTicksNS() - generationStartTime) * 0.0000000001);
logGenerationTime = false;
} }
} }