JobSystem now automatically free Jobs (#91)

* JobSystem now automatically free Jobs

Until now Job allocation used a linear allocator
strategy which required to “reset” the JobSystem
periodically — typically once per frame in
filament.

This is no longer required. We use a pool allocator
now, which doesn’t add much overhead. It does
use a spin-lock for thread-safety though, since
we assume very little contention, this shouldn’t
be a problem.

* Thread Safe Object Pool Allocator

A lock-less, thread-safe object pool allocator,
now used for storing JobSystem’s jobs allocations.
This gets rid of the spin-lock introduced in the
previous cl.
This commit is contained in:
Mathias Agopian
2018-08-13 22:25:15 -07:00
committed by Romain Guy
parent bb553bf53d
commit d9ba3998d2
4 changed files with 140 additions and 97 deletions

View File

@@ -24,6 +24,7 @@
#include <thread>
#include <vector>
#include <utils/Allocator.h>
#include <utils/architecture.h>
#include <utils/Condition.h>
#include <utils/Log.h>
@@ -53,9 +54,9 @@ public:
using JobFunc = void(*)(void*, JobSystem&, Job*);
class Job {
class alignas(CACHELINE_SIZE) Job { // NOLINT(cppcoreguidelines-pro-type-member-init)
public:
Job() noexcept { }
Job() noexcept {} // = default;
Job(const Job&) = delete;
Job(Job&&) = delete;
@@ -92,18 +93,13 @@ public:
// part of a Jobsystem.
static JobSystem* getJobSystem() noexcept;
// Free-up all allocated jobs (without calling destructors)
// Make sure to call this when all call to wait() have returned.
// Also clears the master job
void reset() noexcept;
// resets a job
void reset(Job* job) noexcept;
// If a parent is not specified when creating a job, that job will automatically take the
// master job as a parent.
// The master job is reset when calling reset()
Job* setMasterJob(Job* job) noexcept { return (mMasterJob = job); }
Job* setMasterJob(Job* job) noexcept { return mMasterJob = job; }
// Clears the master job
void reset() noexcept { mMasterJob = nullptr; }
// NOTE: All methods below must be called from the same thread and that thread must be
@@ -241,7 +237,6 @@ public:
void finish(Job* job) noexcept;
// for debugging
size_t getJobWatermark() const noexcept { return mJobWaterMark; }
friend utils::io::ostream& operator << (utils::io::ostream& out, JobSystem const& js);
@@ -305,27 +300,29 @@ private:
void loop(ThreadState* threadState) noexcept;
bool execute(JobSystem::ThreadState& state) noexcept;
void put(WorkQueue& workQueue, Job* job) noexcept { workQueue.push(jobToIndex(job)); }
Job* pop(WorkQueue& workQueue) noexcept { return indexToJob(workQueue.pop()); }
Job* steal(WorkQueue& workQueue) noexcept { return indexToJob(workQueue.steal()); }
Job* indexToJob(size_t index) {
assert(index <= MAX_JOB_COUNT);
return !index ? nullptr : (&mJobStorage[0] - 1) + index;
void put(WorkQueue& workQueue, Job* job) noexcept {
size_t index = job - mJobStorageBase;
assert(index >= 0 && index < MAX_JOB_COUNT);
workQueue.push(uint16_t(index + 1));
}
uint16_t jobToIndex(Job const* job) const {
// we offset index by +1 so b/c workQueue returns 0 on failure
size_t index = job - (&mJobStorage[0] - 1);
assert(index > 0 && index <= MAX_JOB_COUNT);
return uint16_t(index);
Job* pop(WorkQueue& workQueue) noexcept {
size_t index = workQueue.pop();
assert(index <= MAX_JOB_COUNT);
return !index ? nullptr : (mJobStorageBase - 1) + index;
}
Job* steal(WorkQueue& workQueue) noexcept {
size_t index = workQueue.steal();
assert(index <= MAX_JOB_COUNT);
return !index ? nullptr : (mJobStorageBase - 1) + index;
}
// these have thread contention, keep them together
utils::Mutex mLock;
utils::Condition mCondition;
std::atomic<uint32_t> mActiveJobs = { 0 };
std::atomic<uint16_t> mNextJobIndex = { 0 };
utils::Arena<utils::ThreadSafeObjectPoolAllocator<Job>, LockingPolicy::NoLock> mJobPool;
template <typename T>
using aligned_vector = std::vector<T, utils::STLAlignedAllocator<T>>;
@@ -340,13 +337,11 @@ private:
aligned_vector<ThreadState> mThreadStates; // actual data is stored offline
std::atomic<bool> mExitRequested = { 0 }; // this one is almost never written
std::atomic<uint16_t> mAdoptedThreads = { 0 }; // this one is almost never written
Job* mJobStorage = nullptr;
uint16_t mThreadCount = 0;
uint8_t mParallelSplitCount = 0;
Job* const mJobStorageBase; // Base for conversion to indices
uint16_t mThreadCount = 0; // total # of threads in the pool
uint8_t mParallelSplitCount = 0; // # of split allowable in parallel_for
Job* mMasterJob = nullptr;
size_t mJobWaterMark = 0;
static UTILS_DECLARE_TLS(ThreadState *) sThreadState;
};