BluFedora Job System v1.0.0
This is a C++ job system library for use in game engines.
job Namespace Reference

Namespaces

namespace  internal
 

Classes

struct  Counter
 The only syncronization mechanism. Allows you to wait on tasks you asssociated with this counter. More...
 
struct  Ctx
 
struct  InitializationLock
 
struct  JobSystemContext
 
struct  JobSystemCreateOptions
 The runtime configuration for the Job System. More...
 
struct  JobSystemMemoryRequirements
 The memory requirements for a given configuration JobSystemCreateOptions. More...
 
class  LockedQueue
 
class  MPMCQueue
 
struct  Splitter
 
class  SPMCDeque
 
class  SPSCQueue
 
struct  Task
 
union  TaskMemoryBlock
 
struct  TaskPool
 
struct  TaskPtr
 
struct  ThreadLocalState
 

Typedefs

using WorkerID = std::uint16_t
 The id type of each worker thread. More...
 
using TaskHandle = std::uint16_t
 
using TaskHandleType = TaskHandle
 
using AtomicTaskHandleType = std::atomic< TaskHandle >
 
using WorkerIDType = WorkerID
 
using AtomicInt32 = std::atomic_int32_t
 
using Byte = unsigned char
 
using AtomicTaskPtr = std::atomic< job::TaskPtr >
 

Enumerations

enum class  QueueMode : std::uint8_t { Default , WorkerOnly }
 Determines which threads the task will be allowed to run on. More...
 
enum class  SPMCDequeStatus { SUCCESS , FAILED_RACE , FAILED_SIZE }
 

Functions

std::size_t NumSystemThreads () noexcept
 Makes system calls to grab the number threads / processors on the device. This function can be called by any thread concurrently. More...
 
void Initialize (const JobSystemMemoryRequirements &memory_requirements={}, void *const memory=nullptr) noexcept
 Sets up the Job system and creates all the worker threads. The thread that calls 'job::Initialize' is considered the main thread. More...
 
const char * ProcessorArchitectureName () noexcept
 An implementation defined name for the CPU architecture of the device. This function can be called by any thread concurrently. More...
 
std::uint16_t NumWorkers () noexcept
 Returns the number of workers created by the system. This function can be called by any thread concurrently. More...
 
WorkerID CurrentWorker () noexcept
 The current id of the current thread. This function can be called by any thread concurrently. More...
 
bool IsMainThread () noexcept
 Allows for querying if we are currently executing in the main thread. More...
 
void Shutdown () noexcept
 This will deallocate any memory used by the system and shutdown any threads created by 'bfjob::initialize'. More...
 
template<typename Closure >
void Dispatch (const char *const name, Counter *const counter, const Closure &Callback, const QueueMode queue=QueueMode::Default) noexcept
 Main API entrypoint, Pushes a task onto the queue. More...
 
void WaitOn (const Counter &counter) noexcept
 Blocks until all tasks associated with counter are done while This function will block but do work while being blocked. More...
 
void PauseProcessor () noexcept
 CPU pause instruction to indicate when you are in a spin wait loop. More...
 
void YieldTimeSlice () noexcept
 Asks the OS to yield this threads execution to another thread on the current cpu core. More...
 
template<typename F , typename S >
void ParallelFor (const char *const name, Counter *const counter, const std::size_t start, const std::size_t count, S &&splitter, F &&fn, const QueueMode queue=QueueMode::Default)
 Parallel for algorithm, splits the work up recursively splitting based on the splitter passed in. More...
 
template<typename T , typename F , typename S >
void ParallelFor (const char *const name, Counter *const counter, T *const data, const std::size_t count, S &&splitter, F &&fn, const QueueMode queue=QueueMode::Default)
 
template<typename... F>
void ParallelInvoke (const char *const name, Counter *const counter, const QueueMode queue, F &&... fns)
 Invokes each passed in function object in parallel. More...
 
template<typename Splitter , typename Reducer >
void ParallelReduce (const char *const name, Counter *const counter, const std::size_t start, const std::size_t count, Splitter &&splitter, Reducer &&reduce, const QueueMode queue=QueueMode::Default)
 

Variables

static constexpr std::size_t k_FalseSharingPadSize = std::hardware_destructive_interference_size
 
static constexpr std::size_t k_CachelineSize = 64u
 
static constexpr std::size_t k_ExpectedTaskSize = std::max(std::size_t(128u), k_CachelineSize)
 
static constexpr TaskHandle NullTaskHandle = std::numeric_limits<TaskHandle>::max()
 

Class Documentation

◆ job::Counter

struct job::Counter

The only syncronization mechanism. Allows you to wait on tasks you asssociated with this counter.

See also
job::WaitOn

Definition at line 48 of file job_api.hpp.

Class Members
atomic_uint64_t unfinished_tasks

◆ job::InitializationLock

struct job::InitializationLock

Definition at line 175 of file job_system.cpp.

Class Members
mutex init_mutex
condition_variable init_cv
atomic_uint32_t num_workers_ready

◆ job::JobSystemContext

struct job::JobSystemContext

Definition at line 182 of file job_system.cpp.

Class Members
ThreadLocalState * workers
uint32_t num_workers
atomic_uint32_t num_user_threads_setup
uint32_t num_tasks_per_worker
InitializationLock init_lock
const char * sys_arch_str
size_t system_alloc_size
size_t system_alloc_alignment
bool needs_delete
atomic_bool is_running
mutex worker_sleep_mutex
condition_variable worker_sleep_cv
atomic_uint32_t num_available_jobs

◆ job::JobSystemCreateOptions

struct job::JobSystemCreateOptions

The runtime configuration for the Job System.

Definition at line 86 of file job_api.hpp.

Class Members
uint16_t num_threads Use 0 to indicate using the number of cores available on the system.
uint16_t normal_queue_size Number of tasks in each worker's QueueType::Default queue. (Must be power of two)
uint16_t worker_queue_size Number of tasks in each worker's QueueType::WorkerOnly queue. (Must be power of two)
uint64_t job_steal_rng_seed The RNG for work queue stealing will be seeded with this value.

◆ job::TaskMemoryBlock

union job::TaskMemoryBlock

Definition at line 150 of file job_system.cpp.

Class Members
TaskMemoryBlock * next
unsigned char storage[sizeof(Task)]

◆ job::TaskPool

struct job::TaskPool

Definition at line 157 of file job_system.cpp.

Class Members
TaskMemoryBlock * memory
TaskMemoryBlock * freelist

◆ job::ThreadLocalState

struct job::ThreadLocalState

Definition at line 163 of file job_system.cpp.

Class Members
SPMCDeque< TaskPtr > normal_queue
SPMCDeque< TaskPtr > worker_queue
TaskPool task_allocator
TaskHandle * allocated_tasks
TaskHandleType num_allocated_tasks
ThreadLocalState * last_stolen_worker
pcg_state_setseq_64 rng_state
thread thread_id

Typedef Documentation

◆ WorkerID

using job::WorkerID = typedef std::uint16_t

The id type of each worker thread.

Definition at line 39 of file job_api.hpp.

◆ TaskHandle

using job::TaskHandle = typedef std::uint16_t

Definition at line 79 of file job_system.cpp.

◆ TaskHandleType

Definition at line 80 of file job_system.cpp.

◆ AtomicTaskHandleType

using job::AtomicTaskHandleType = typedef std::atomic<TaskHandle>

Definition at line 81 of file job_system.cpp.

◆ WorkerIDType

using job::WorkerIDType = typedef WorkerID

Definition at line 82 of file job_system.cpp.

◆ AtomicInt32

using job::AtomicInt32 = typedef std::atomic_int32_t

Definition at line 83 of file job_system.cpp.

◆ Byte

using job::Byte = typedef unsigned char

Definition at line 84 of file job_system.cpp.

◆ AtomicTaskPtr

using job::AtomicTaskPtr = typedef std::atomic<job::TaskPtr>

Definition at line 112 of file job_system.cpp.

Enumeration Type Documentation

◆ QueueMode

enum class job::QueueMode : std::uint8_t
strong

Determines which threads the task will be allowed to run on.

Enumerator
Default 

Tasks in this queue will run on either the main or worker threads.

WorkerOnly 

Tasks in this queue will never run on the main thread.

Definition at line 64 of file job_api.hpp.

65 {
66 Default,
68 };
@ Default
Tasks in this queue will run on either the main or worker threads.
@ WorkerOnly
Tasks in this queue will never run on the main thread.

◆ SPMCDequeStatus

enum class job::SPMCDequeStatus
strong
Enumerator
SUCCESS 

Returned from Push, Pop and Steal.

FAILED_RACE 

Returned from Pop and Steal.

FAILED_SIZE 

Returned from Push, Pop and Steal.

Definition at line 228 of file job_queue.hpp.

229 {
230 SUCCESS,
233 };
@ FAILED_RACE
Returned from Pop and Steal.
@ FAILED_SIZE
Returned from Push, Pop and Steal.
@ SUCCESS
Returned from Push, Pop and Steal.

Function Documentation

◆ NumSystemThreads()

std::size_t job::NumSystemThreads ( )
noexcept

Makes system calls to grab the number threads / processors on the device. This function can be called by any thread concurrently.

Can be called even if the job system has not been initialized.

Returns
std::size_t The number threads / processors on the computer.

Definition at line 811 of file job_system.cpp.

812{
813#if IS_SINGLE_THREADED
814 return 1;
815#else
816 const auto n = std::thread::hardware_concurrency();
817 return n != 0 ? n : 1;
818#endif
819
820#if 0
821
822#if IS_WINDOWS
823 SYSTEM_INFO sysinfo;
824 GetSystemInfo(&sysinfo);
825 return sysinfo.dwNumberOfProcessors;
826#elif IS_POSIX
827 return sysconf(_SC_NPROCESSORS_ONLN) /* * 2*/;
828#elif 0 // FreeBSD, MacOS X, NetBSD, OpenBSD
829 nt mib[4];
830 int numCPU;
831 std::size_t len = sizeof(numCPU);
832
833 /* set the mib for hw.ncpu */
834 mib[0] = CTL_HW;
835 mib[1] = HW_AVAILCPU; // alternatively, try HW_NCPU;
836
837 /* get the number of CPUs from the system */
838 sysctl(mib, 2, &numCPU, &len, NULL, 0);
839
840 if (numCPU < 1)
841 {
842 mib[1] = HW_NCPU;
843 sysctl(mib, 2, &numCPU, &len, NULL, 0);
844 if (numCPU < 1)
845 numCPU = 1;
846 }
847
848 return numCPU;
849#elif 0 // HPUX
850 return mpctl(MPC_GETNUMSPUS, NULL, NULL);
851#elif 0 // IRIX
852 return sysconf(_SC_NPROC_ONLN);
853#elif 0 // Objective-C (Mac OS X >=10.5 or iOS)
854 NSUInteger a = [[NSProcessInfo processInfo] processorCount];
855 NSUInteger b = [[NSProcessInfo processInfo] activeProcessorCount];
856
857 return a;
858#endif
859
860#endif
861}

◆ Initialize()

void job::Initialize ( const JobSystemMemoryRequirements memory_requirements = {},
void *const  memory = nullptr 
)
noexcept

Sets up the Job system and creates all the worker threads. The thread that calls 'job::Initialize' is considered the main thread.

Parameters
memory_requirementsThe customization parameters to initialize the system with. To be gotten from job::MemRequirementsForConfig.
memoryMust be memory_requirements.byte_size in size and with alignment memory_requirements.alignment. If nullptr then the system heap will be used.

Definition at line 703 of file job_system.cpp.

704{
705 JobAssert(g_JobSystem == nullptr, "Already initialized.");
706
707 const bool needs_delete = memory == nullptr;
708
709 if (!memory)
710 {
711 memory = ::operator new[](memory_requirements.byte_size, std::align_val_t{memory_requirements.alignment});
712 }
713
714 JobAssert(memory != nullptr, "memory must be a valid pointer.");
715 JobAssert(IsPointerAligned(memory, memory_requirements.alignment), "memory must be a aligned to `memory_requirements.alignment`.");
716
717 const JobSystemCreateOptions& options = memory_requirements.options;
718 const std::uint64_t rng_seed = options.job_steal_rng_seed;
719 const WorkerID num_threads = config::WorkerCount(options);
720 const std::uint16_t num_tasks_per_worker = config::NumTasksPerWorker(options);
721 const std::uint32_t total_num_tasks = config::TotalNumTasks(num_threads, num_tasks_per_worker);
722
723 void* alloc_ptr = memory;
724 JobSystemContext* job_system = LinearAlloc<JobSystemContext>(alloc_ptr, 1u).ptr;
725 Span<job::ThreadLocalState> all_workers = LinearAlloc<job::ThreadLocalState>(alloc_ptr, num_threads);
726 Span<TaskMemoryBlock> all_tasks = LinearAlloc<TaskMemoryBlock>(alloc_ptr, total_num_tasks);
727 Span<AtomicTaskPtr> worker_task_ptrs = LinearAlloc<AtomicTaskPtr>(alloc_ptr, total_num_tasks);
728 Span<TaskHandle> all_task_handles = LinearAlloc<TaskHandle>(alloc_ptr, total_num_tasks);
729
730 job_system->workers = all_workers.ptr;
731 job_system->num_workers = num_threads;
732 job_system->num_user_threads_setup.store(0, std::memory_order_relaxed);
733 job_system->num_tasks_per_worker = num_tasks_per_worker;
734 job_system->sys_arch_str = "Unknown Arch";
735 job_system->num_available_jobs.store(0, std::memory_order_relaxed);
736 job_system->needs_delete = needs_delete;
737 job_system->system_alloc_size = memory_requirements.byte_size;
738 job_system->system_alloc_alignment = memory_requirements.alignment;
739 job_system->init_lock.num_workers_ready.store(1u, std::memory_order_relaxed); // Main thread already initialized.
740
741#if IS_WINDOWS
742 SYSTEM_INFO sysinfo;
743 GetSystemInfo(&sysinfo);
744
745 switch (sysinfo.wProcessorArchitecture)
746 {
747 case PROCESSOR_ARCHITECTURE_AMD64:
748 {
749 job_system->sys_arch_str = "x64 (Intel or AMD)";
750 break;
751 }
752 case PROCESSOR_ARCHITECTURE_ARM:
753 {
754 job_system->sys_arch_str = "ARM";
755 break;
756 }
757 case PROCESSOR_ARCHITECTURE_ARM64:
758 {
759 job_system->sys_arch_str = "ARM64";
760 break;
761 }
762 case PROCESSOR_ARCHITECTURE_IA64:
763 {
764 job_system->sys_arch_str = "Intel Itanium-Based";
765 break;
766 }
767 case PROCESSOR_ARCHITECTURE_INTEL:
768 {
769 job_system->sys_arch_str = "Intel x86";
770 break;
771 }
772 case PROCESSOR_ARCHITECTURE_UNKNOWN:
773 default:
774 {
775 job_system->sys_arch_str = "Unknown Arch";
776 break;
777 }
778 }
779#endif
780
781 job::ThreadLocalState* const main_thread_worker = job_system->workers;
782
783 for (std::uint64_t worker_index = 0; worker_index < num_threads; ++worker_index)
784 {
785 job::ThreadLocalState* const worker = SpanAlloc(&all_workers, 1u);
786
787 worker->normal_queue.Initialize(SpanAlloc(&worker_task_ptrs, options.normal_queue_size), options.normal_queue_size);
788 worker->worker_queue.Initialize(SpanAlloc(&worker_task_ptrs, options.worker_queue_size), options.worker_queue_size);
789 task_pool::Initialize(&worker->task_allocator, SpanAlloc(&all_tasks, num_tasks_per_worker), num_tasks_per_worker);
790 worker->allocated_tasks = SpanAlloc(&all_task_handles, num_tasks_per_worker);
791 worker->num_allocated_tasks = 0u;
792 pcg32_srandom_r(&worker->rng_state, worker_index + rng_seed, worker_index * 2u + 1u + rng_seed);
793 worker->last_stolen_worker = main_thread_worker;
794 }
795
796 g_JobSystem = job_system;
797 g_CurrentWorker = main_thread_worker;
798
799 std::atomic_thread_fence(std::memory_order_release);
800 for (std::uint64_t worker_index = 1; worker_index < num_threads; ++worker_index)
801 {
802 worker::InitializeThread(job_system->workers + worker_index);
803 }
804
805 JobAssert(all_workers.num_elements == 0u, "All elements expected to be allocated out.");
806 JobAssert(all_tasks.num_elements == 0u, "All elements expected to be allocated out.");
807 JobAssert(worker_task_ptrs.num_elements == 0u, "All elements expected to be allocated out.");
808 JobAssert(all_task_handles.num_elements == 0u, "All elements expected to be allocated out.");
809}
#define JobAssert(expr, msg)
Definition: job_api.hpp:32
static thread_local job::ThreadLocalState * g_CurrentWorker
Definition: job_system.cpp:208
static job::JobSystemContext * g_JobSystem
Definition: job_system.cpp:207
std::uint64_t job_steal_rng_seed
The RNG for work queue stealing will be seeded with this value.
Definition: job_api.hpp:91
std::uint16_t WorkerID
The id type of each worker thread.
Definition: job_api.hpp:39
void Initialize(const JobSystemMemoryRequirements &memory_requirements={}, void *const memory=nullptr) noexcept
Sets up the Job system and creates all the worker threads. The thread that calls 'job::Initialize' is...
Definition: job_system.cpp:703
std::size_t byte_size
The number of bytes the job system needed.
Definition: job_api.hpp:101
std::size_t alignment
The base alignment the pointer should be.
Definition: job_api.hpp:102
JobSystemCreateOptions options
The options used to create the memory requirements.
Definition: job_api.hpp:100

References g_CurrentWorker, g_JobSystem, job::JobSystemContext::init_lock, Initialize(), job::JobSystemCreateOptions::job_steal_rng_seed, JobAssert, job::JobSystemContext::needs_delete, job::JobSystemCreateOptions::normal_queue_size, job::JobSystemContext::num_available_jobs, job::JobSystemContext::num_tasks_per_worker, job::JobSystemContext::num_user_threads_setup, job::JobSystemContext::num_workers, job::InitializationLock::num_workers_ready, job::JobSystemContext::sys_arch_str, job::JobSystemContext::system_alloc_alignment, job::JobSystemContext::system_alloc_size, job::JobSystemCreateOptions::worker_queue_size, and job::JobSystemContext::workers.

Referenced by Initialize().

◆ ProcessorArchitectureName()

const char * job::ProcessorArchitectureName ( )
noexcept

An implementation defined name for the CPU architecture of the device. This function can be called by any thread concurrently.

Returns
const char* Nul terminated name for the CPU architecture of the device.

Definition at line 868 of file job_system.cpp.

869{
871}
const char * sys_arch_str
Definition: job_system.cpp:191

References g_JobSystem, and job::JobSystemContext::sys_arch_str.

◆ NumWorkers()

std::uint16_t job::NumWorkers ( )
noexcept

Returns the number of workers created by the system. This function can be called by any thread concurrently.

Returns
std::size_t The number of workers created by the system.

Definition at line 863 of file job_system.cpp.

864{
865 return std::uint16_t(g_JobSystem->num_workers);
866}
std::uint32_t num_workers
Definition: job_system.cpp:187

References g_JobSystem, and job::JobSystemContext::num_workers.

Referenced by job::internal::DispatchImpl(), and job::Splitter::EvenSplit().

◆ CurrentWorker()

job::WorkerID job::CurrentWorker ( )
noexcept

The current id of the current thread. This function can be called by any thread concurrently.

The main thread will always be 0.

Returns
WorkerID The current id of the current thread.

Definition at line 873 of file job_system.cpp.

874{
875 JobAssert(g_CurrentWorker != nullptr, "This thread was not created by the job system.");
877}
job::ThreadLocalState * workers
Definition: job_system.cpp:186

References g_CurrentWorker, g_JobSystem, JobAssert, and job::JobSystemContext::workers.

Referenced by WaitOn().

◆ IsMainThread()

bool job::IsMainThread ( )
noexcept

Allows for querying if we are currently executing in the main thread.

Returns
True if we are in the main thread, false otherwise.
Warning
Must only be called from a thread registered with the job system.

Definition at line 879 of file job_system.cpp.

880{
882}
bool IsMainThread() noexcept
Allows for querying if we are currently executing in the main thread.
Definition: job_system.cpp:879

References g_CurrentWorker, and IsMainThread().

Referenced by IsMainThread().

◆ Shutdown()

void job::Shutdown ( )
noexcept

This will deallocate any memory used by the system and shutdown any threads created by 'bfjob::initialize'.

Warning
This function may only be called by the main thread.

Definition at line 884 of file job_system.cpp.

885{
886 JobAssert(g_JobSystem != nullptr, "Cannot shutdown when never initialized.");
887
888 static_assert(std::is_trivially_destructible_v<TaskMemoryBlock>, "TaskMemoryBlock's destructor not called.");
889 static_assert(std::is_trivially_destructible_v<job::TaskPtr>, "job::TaskPtr's destructor not called.");
890 static_assert(std::is_trivially_destructible_v<AtomicTaskPtr>, "AtomicTaskPtr's destructor not called.");
891 static_assert(std::is_trivially_destructible_v<TaskHandle>, "TaskHandle's destructor not called.");
892
893 JobSystemContext* const job_system = g_JobSystem;
894 const std::uint32_t num_workers = job_system->num_workers;
895
896 // Incase all threads are not initialized by the time shutdown is called.
897 while (job_system->is_running.load(std::memory_order_relaxed) != true) {}
898
899 {
900 std::unique_lock<std::mutex> lock(job_system->worker_sleep_mutex);
901 job_system->is_running.store(false, std::memory_order_relaxed);
902 }
903
904 // Allow one last update loop to allow them to end.
905 system::WakeUpAllWorkers();
906
907 for (std::uint32_t i = 0; i < num_workers; ++i)
908 {
909 job::ThreadLocalState* const worker = job_system->workers + i;
910
911 if (i != 0)
912 {
913 worker::ShutdownThread(worker);
914 }
915
916 worker->~ThreadLocalState();
917 }
918
919 const bool needs_delete = job_system->needs_delete;
920
921 job_system->~JobSystemContext();
922 g_CurrentWorker = nullptr;
923 g_JobSystem = nullptr;
924
925 if (needs_delete)
926 {
927 ::operator delete[](job_system, job_system->system_alloc_size, std::align_val_t{job_system->system_alloc_alignment});
928 }
929}
std::atomic_bool is_running
Definition: job_system.cpp:195
std::mutex worker_sleep_mutex
Definition: job_system.cpp:199
std::size_t system_alloc_size
Definition: job_system.cpp:192

References g_CurrentWorker, g_JobSystem, job::JobSystemContext::is_running, JobAssert, job::JobSystemContext::needs_delete, job::JobSystemContext::num_workers, job::JobSystemContext::system_alloc_size, job::JobSystemContext::worker_sleep_mutex, and job::JobSystemContext::workers.

◆ Dispatch()

template<typename Closure >
void job::Dispatch ( const char *const  name,
Counter *const  counter,
const Closure &  Callback,
const QueueMode  queue = QueueMode::Default 
)
noexcept

Main API entrypoint, Pushes a task onto the queue.

Template Parameters
ClosureCallable Type with void Closure(const job::Ctx& ctx);.
Parameters
nameOptional name for the task, can be null. Not used internally for anything.
counterThe counter to associate the job with.
CallbackCallable with [](const job::Ctx& ctx) -> void {}.
queueWhich queue to push the task to.
Warning
If the no free tasks are avaiable from the pool the task will run inline of this thread regardles of the queue mode.

Definition at line 251 of file job_api.hpp.

252 {
253 const internal::JobFn ErasedCallback = +[](const internal::PrivateCtx& ctx) -> void {
254 alignas(Closure) unsigned char local_user_data[sizeof(Closure)];
255 Closure* const src_user_data = static_cast<Closure*>(ctx.src_user_data);
256 Closure* const typed_callback = ::new (local_user_data) Closure(std::move(*src_user_data));
257
258 src_user_data->~Closure();
259 ctx.ReleaseTaskToPool();
260
261 (*typed_callback)(static_cast<const job::Ctx&>(ctx));
262 typed_callback->~Closure();
263 };
264
265 internal::DispatchImpl(name, counter, queue, ErasedCallback, sizeof(Closure), alignof(Closure), &Callback, +[](void* const dst_user_data, const void* const src_user_data) -> void {
266 ::new (dst_user_data) Closure(*static_cast<const Closure*>(src_user_data));
267 });
268 }
void(*)(const PrivateCtx &ctx) JobFn
Definition: job_api.hpp:234
void DispatchImpl(const char *const name, Counter *const counter, const QueueMode queue, const JobFn func, const std::size_t user_data_size, const std::size_t user_data_alignment, const void *const user_data, void(*InitUserData)(void *const user_data, const void *const in_user_data)) noexcept
Definition: job_system.cpp:947

References job::internal::DispatchImpl().

Referenced by ParallelFor(), ParallelInvoke(), and ParallelReduce().

◆ WaitOn()

void job::WaitOn ( const Counter counter)
noexcept

Blocks until all tasks associated with counter are done while This function will block but do work while being blocked.

Parameters
counterThe counter to wait for on.

Definition at line 931 of file job_system.cpp.

932{
933 const WorkerID worker_id = CurrentWorker();
934
935 system::WakeUpAllWorkers();
936
937 job::ThreadLocalState* const worker = system::GetWorker(worker_id);
938
939 while (counter.unfinished_tasks.load(std::memory_order_acquire) != 0u)
940 {
941 worker::TryRunTask(worker);
942 }
943}
std::atomic_uint64_t unfinished_tasks
Definition: job_api.hpp:50
WorkerID CurrentWorker() noexcept
The current id of the current thread. This function can be called by any thread concurrently.
Definition: job_system.cpp:873

References CurrentWorker(), and job::Counter::unfinished_tasks.

Referenced by ParallelReduce().

◆ PauseProcessor()

void job::PauseProcessor ( )
noexcept

CPU pause instruction to indicate when you are in a spin wait loop.

Definition at line 1051 of file job_system.cpp.

1052{
1053 NativePause();
1054}
#define NativePause

References NativePause.

Referenced by job::MPMCQueue::Commit().

◆ YieldTimeSlice()

void job::YieldTimeSlice ( )
noexcept

Asks the OS to yield this threads execution to another thread on the current cpu core.

Definition at line 1058 of file job_system.cpp.

1059{
1060 // Windows : SwitchToThread()
1061 // Linux : sched_yield()
1062 std::this_thread::yield();
1063}

◆ ParallelFor() [1/2]

template<typename F , typename S >
void job::ParallelFor ( const char *const  name,
Counter *const  counter,
const std::size_t  start,
const std::size_t  count,
S &&  splitter,
F &&  fn,
const QueueMode  queue = QueueMode::Default 
)

Parallel for algorithm, splits the work up recursively splitting based on the splitter passed in.

Assumes all callable objects passed in can be invoked on multiple threads at the same time.

Template Parameters
FType of function object passed in. Must be callable like: fn(const job::Ctx& ctx, const std::size_t index)
SCallable splitter, must be callable like: splitter(std::size_t count)
Parameters
startStart index for the range to be parallelized.
countstart + count defines the end range.
splitterCallable splitter, must be callable like: splitter(std::size_t count)
fnFunction object must be callable like: fn(const job::Ctx& ctx, const std::size_t index)
parentParent task to add this task as a child of.
Returns
The new task holding the work of the parallel for.

Definition at line 354 of file job_api.hpp.

355 {
356 job::Dispatch(name, counter, [=, splitter = std::forward<S>(splitter), fn = std::forward<F>(fn)](const job::Ctx& ctx) -> void {
357 if (count > 1u && splitter(count))
358 {
359 const std::size_t left_count = count / 2;
360 const std::size_t right_count = count - left_count;
361
362 job::ParallelFor(ctx.task_name, ctx.task_counter, start + 0, left_count, splitter, fn, queue);
363 job::ParallelFor(ctx.task_name, ctx.task_counter, start + left_count, right_count, splitter, fn, queue);
364 }
365 else
366 {
367 for (std::size_t offset = 0u; offset < count; ++offset)
368 {
369 fn(ctx, start + offset);
370 }
371 } }, queue);
372 }
void ParallelFor(const char *const name, Counter *const counter, const std::size_t start, const std::size_t count, S &&splitter, F &&fn, const QueueMode queue=QueueMode::Default)
Parallel for algorithm, splits the work up recursively splitting based on the splitter passed in.
Definition: job_api.hpp:354
void Dispatch(const char *const name, Counter *const counter, const Closure &Callback, const QueueMode queue=QueueMode::Default) noexcept
Main API entrypoint, Pushes a task onto the queue.
Definition: job_api.hpp:251
Counter * task_counter
The counter this task will decrement when done.
Definition: job_api.hpp:55
const char * task_name
The debug name of the task.
Definition: job_api.hpp:57

References Dispatch(), ParallelFor(), job::Ctx::task_counter, and job::Ctx::task_name.

Referenced by ParallelFor(), and ParallelReduce().

◆ ParallelFor() [2/2]

template<typename T , typename F , typename S >
void job::ParallelFor ( const char *const  name,
Counter *const  counter,
T *const  data,
const std::size_t  count,
S &&  splitter,
F &&  fn,
const QueueMode  queue = QueueMode::Default 
)

Definition at line 375 of file job_api.hpp.

376 {
377 return job::ParallelFor(name, counter, std::size_t(0), count, std::forward<S>(splitter), [=](const job::Ctx& ctx, const std::size_t index) { fn(ctx, data + index); }, queue);
378 }

References ParallelFor().

◆ ParallelInvoke()

template<typename... F>
void job::ParallelInvoke ( const char *const  name,
Counter *const  counter,
const QueueMode  queue,
F &&...  fns 
)

Invokes each passed in function object in parallel.

Template Parameters
...FThe function objects types. Must be callable like: fn(Task* task)
Parameters
parentParent task to add this task as a child of.
...fnsFunction objects must be callable like: fn(Task* task)
Returns
The new task holding the work of the parallel invoke.

Definition at line 398 of file job_api.hpp.

399 {
400 (job::Dispatch(name, counter, std::forward<F>(fns), queue), ...);
401 }

References Dispatch().

◆ ParallelReduce()

template<typename Splitter , typename Reducer >
void job::ParallelReduce ( const char *const  name,
Counter *const  counter,
const std::size_t  start,
const std::size_t  count,
Splitter &&  splitter,
Reducer &&  reduce,
const QueueMode  queue = QueueMode::Default 
)

Definition at line 404 of file job_api.hpp.

405 {
406 const auto ParallelReduce_Impl = [=, splitter = std::forward<Splitter>(splitter), reduce = std::forward<Reducer>(reduce)](const job::Ctx& ctx) -> void {
407 // NOTE(SR):
408 // Could also have a stride that increases each step.
409 // This would be bad for Cuda GPU (Shared Memory Bank Conflict)
410 // But good on CPU with better locality.
411 // https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf
412
413 std::size_t count_left = count;
414
415 while (count_left > 1)
416 {
417 const std::size_t stride = count_left / 2;
418
419 const auto ReduceRange = [stride, &reduce](const job::Ctx& ctx, const std::size_t index) -> void {
420 reduce(ctx, index, index + stride);
421 };
422
423 Counter c{};
424 ParallelFor(name, &c, start, stride, splitter, ReduceRange, queue);
425 WaitOn(c);
426
427 if ((count_left & 1) != 0)
428 {
429 reduce(ctx, start, start + count_left - 1);
430 }
431
432 count_left = stride;
433 }
434 };
435
436 job::Dispatch(name, counter, ParallelReduce_Impl, queue);
437 }
void WaitOn(const Counter &counter) noexcept
Blocks until all tasks associated with counter are done while This function will block but do work wh...
Definition: job_system.cpp:931
void ParallelFor(const char *const name, Counter *const counter, T *const data, const std::size_t count, S &&splitter, F &&fn, const QueueMode queue=QueueMode::Default)
Definition: job_api.hpp:375

References Dispatch(), ParallelFor(), and WaitOn().

Variable Documentation

◆ k_FalseSharingPadSize

constexpr std::size_t job::k_FalseSharingPadSize = std::hardware_destructive_interference_size
staticconstexpr

Definition at line 29 of file job_queue.hpp.

◆ k_CachelineSize

constexpr std::size_t job::k_CachelineSize = 64u
staticconstexpr

Definition at line 72 of file job_system.cpp.

◆ k_ExpectedTaskSize

constexpr std::size_t job::k_ExpectedTaskSize = std::max(std::size_t(128u), k_CachelineSize)
staticconstexpr

Definition at line 75 of file job_system.cpp.

◆ NullTaskHandle

constexpr TaskHandle job::NullTaskHandle = std::numeric_limits<TaskHandle>::max()
staticconstexpr

Definition at line 86 of file job_system.cpp.

Referenced by job::TaskPtr::isNull(), and job::TaskPtr::job::TaskPtr().