BluFedora Job System v1.0.0
This is a C++ job system library for use in game engines.
job_api.hpp
Go to the documentation of this file.
1/******************************************************************************/
18/******************************************************************************/
19#ifndef JOB_API_HPP
20#define JOB_API_HPP
21
22#include <atomic> // atomic_uint64_t
23#include <cstdint> // sized integer types
24#include <new> // placement new
25#include <utility> // forward, move
26
27#ifndef JOB_SYS_ASSERTIONS
28#define JOB_SYS_ASSERTIONS 1
29#endif
30
31#if JOB_SYS_ASSERTIONS
32#define JobAssert(expr, msg) (::job::internal::AssertHandler)((expr), __FILE__, __LINE__, msg)
33#else
34#define JobAssert(expr, msg)
35#endif
36
37namespace job
38{
39 using WorkerID = std::uint16_t;
40
48 struct Counter
49 {
50 std::atomic_uint64_t unfinished_tasks = 0u;
51 };
52
53 struct Ctx
54 {
55 Counter* task_counter = nullptr;
57 const char* task_name = 0u;
58 };
59
64 enum class QueueMode : std::uint8_t
65 {
66 Default,
68 };
69
80 std::size_t NumSystemThreads() noexcept;
81
87 {
88 std::uint16_t num_threads = 0;
89 std::uint16_t normal_queue_size = 1024;
90 std::uint16_t worker_queue_size = 512;
91 std::uint64_t job_steal_rng_seed = 0u;
92 };
93
99 {
101 std::size_t byte_size;
102 std::size_t alignment;
103
105 };
106
120 void Initialize(const JobSystemMemoryRequirements& memory_requirements = {}, void* const memory = nullptr) noexcept;
121
130 const char* ProcessorArchitectureName() noexcept;
131
140 std::uint16_t NumWorkers() noexcept;
141
152 WorkerID CurrentWorker() noexcept;
153
164 bool IsMainThread() noexcept;
165
174 void Shutdown() noexcept;
175
197 template<typename Closure>
198 void Dispatch(const char* const name, Counter* const counter, const Closure& Callback, const QueueMode queue = QueueMode::Default) noexcept;
199
208 void WaitOn(const Counter& counter) noexcept;
209
214 void PauseProcessor() noexcept;
215
220 void YieldTimeSlice() noexcept;
221
222 // Template Function Implementation //
223
224 namespace internal
225 {
226 struct PrivateCtx : public Ctx
227 {
228 void* src_user_data = nullptr;
229 std::atomic_bool* is_ready_for_gc = nullptr;
230
231 void ReleaseTaskToPool() const;
232 };
233
234 using JobFn = void (*)(const PrivateCtx& ctx);
235
236#if JOB_SYS_ASSERTIONS
237 void AssertHandler(const bool condition, const char* const filename, const int line_number, const char* const msg);
238#endif
239
240 void DispatchImpl(const char* const name,
241 Counter* const counter,
242 const QueueMode queue,
243 const JobFn func,
244 const std::size_t user_data_size,
245 const std::size_t user_data_alignment,
246 const void* const user_data,
247 void (*InitUserData)(void* const user_data, const void* const in_user_data)) noexcept;
248 }
249
250 template<typename Closure>
251 void Dispatch(const char* const name, Counter* const counter, const Closure& Callback, const QueueMode queue) noexcept
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 }
269
270 // Parallel Algorithms API
271
272 struct Splitter
273 {
295 static Splitter EvenSplit(const std::size_t total_num_items, std::size_t num_groups_per_thread = 1u)
296 {
297 if (num_groups_per_thread < 1u)
298 {
299 num_groups_per_thread = 1u;
300 }
301
302 return Splitter{(total_num_items / num_groups_per_thread) / NumWorkers()};
303 }
304
305 static constexpr Splitter MaxItemsPerTask(const std::size_t max_items)
306 {
307 return Splitter{max_items};
308 }
309
310 template<typename T>
311 static constexpr Splitter MaxDataSize(const std::size_t max_data_size)
312 {
313 return Splitter{max_data_size / sizeof(T)};
314 }
315
316 std::size_t max_count = 0u;
317
318 constexpr bool operator()(const std::size_t count) const { return count > max_count; }
319 };
320
353 template<typename F, typename S>
354 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)
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 }
373
374 template<typename T, typename F, typename S>
375 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)
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 }
379
397 template<typename... F>
398 void ParallelInvoke(const char* const name, Counter* const counter, const QueueMode queue, F&&... fns)
399 {
400 (job::Dispatch(name, counter, std::forward<F>(fns), queue), ...);
401 }
402
403 template<typename Splitter, typename Reducer>
404 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)
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 }
438}
439
440#endif // JOB_API_HPP
441
442/******************************************************************************/
443/*
444 MIT License
445
446 Copyright (c) 2020-2026 Shareef Abdoul-Raheem
447
448 Permission is hereby granted, free of charge, to any person obtaining a copy
449 of this software and associated documentation files (the "Software"), to deal
450 in the Software without restriction, including without limitation the rights
451 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
452 copies of the Software, and to permit persons to whom the Software is
453 furnished to do so, subject to the following conditions:
454
455 The above copyright notice and this permission notice shall be included in all
456 copies or substantial portions of the Software.
457
458 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
459 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
460 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
461 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
462 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
463 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
464 SOFTWARE.
465*/
466/******************************************************************************/
void(*)(const PrivateCtx &ctx) JobFn
Definition: job_api.hpp:234
void AssertHandler(const bool condition, const char *const filename, const int line_number, const char *const msg)
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
Definition: job_api.hpp:38
void ParallelInvoke(const char *const name, Counter *const counter, const QueueMode queue, F &&... fns)
Invokes each passed in function object in parallel.
Definition: job_api.hpp:398
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 Shutdown() noexcept
This will deallocate any memory used by the system and shutdown any threads created by 'bfjob::initia...
Definition: job_system.cpp:884
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)
Definition: job_api.hpp:404
bool IsMainThread() noexcept
Allows for querying if we are currently executing in the main thread.
Definition: job_system.cpp:879
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
QueueMode
Determines which threads the task will be allowed to run on.
Definition: job_api.hpp:65
@ 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.
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
std::atomic_uint64_t unfinished_tasks
Definition: job_api.hpp:50
std::uint16_t WorkerID
The id type of each worker thread.
Definition: job_api.hpp:39
WorkerID CurrentWorker() noexcept
The current id of the current thread. This function can be called by any thread concurrently.
Definition: job_system.cpp:873
const char * ProcessorArchitectureName() noexcept
An implementation defined name for the CPU architecture of the device. This function can be called by...
Definition: job_system.cpp:868
std::uint16_t NumWorkers() noexcept
Returns the number of workers created by the system. This function can be called by any thread concur...
Definition: job_system.cpp:863
void PauseProcessor() noexcept
CPU pause instruction to indicate when you are in a spin wait loop.
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 NumSystemThreads() noexcept
Makes system calls to grab the number threads / processors on the device. This function can be called...
Definition: job_system.cpp:811
void YieldTimeSlice() noexcept
Asks the OS to yield this threads execution to another thread on the current cpu core.
The only syncronization mechanism. Allows you to wait on tasks you asssociated with this counter.
Definition: job_api.hpp:49
The runtime configuration for the Job System.
Definition: job_api.hpp:87
WorkerID current_worker
The worker the current task is running on.
Definition: job_api.hpp:56
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
The memory requirements for a given configuration JobSystemCreateOptions.
Definition: job_api.hpp:99
std::size_t byte_size
The number of bytes the job system needed.
Definition: job_api.hpp:101
JobSystemMemoryRequirements(const JobSystemCreateOptions &options={}) noexcept
Definition: job_system.cpp:684
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
static constexpr Splitter MaxItemsPerTask(const std::size_t max_items)
Definition: job_api.hpp:305
std::size_t max_count
Definition: job_api.hpp:316
constexpr bool operator()(const std::size_t count) const
Definition: job_api.hpp:318
static Splitter EvenSplit(const std::size_t total_num_items, std::size_t num_groups_per_thread=1u)
Splits work evenly across the threads depending on the number of workers.
Definition: job_api.hpp:295
static constexpr Splitter MaxDataSize(const std::size_t max_data_size)
Definition: job_api.hpp:311