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 std::size_t index = 0u;
59 };
60
65 enum class QueueMode : std::uint8_t
66 {
67 Default,
69 };
70
81 std::size_t NumSystemThreads() noexcept;
82
88 {
89 std::uint16_t num_threads = 0;
90 std::uint16_t normal_queue_size = 1024;
91 std::uint16_t worker_queue_size = 512;
92 std::uint64_t job_steal_rng_seed = 0u;
93 };
94
100 {
102 std::size_t byte_size;
103 std::size_t alignment;
104
106 };
107
121 void Initialize(const JobSystemMemoryRequirements& memory_requirements = {}, void* const memory = nullptr) noexcept;
122
131 const char* ProcessorArchitectureName() noexcept;
132
141 std::uint16_t NumWorkers() noexcept;
142
153 WorkerID CurrentWorker() noexcept;
154
165 bool IsMainThread() noexcept;
166
175 void Shutdown() noexcept;
176
198 template<typename Closure>
199 void Dispatch(const char* const name, Counter* const counter, const Closure& Callback, const QueueMode queue = QueueMode::Default) noexcept;
200
209 void WaitOn(const Counter& counter) noexcept;
210
215 void PauseProcessor() noexcept;
216
221 void YieldTimeSlice() noexcept;
222
223 // Template Function Implementation //
224
225 namespace internal
226 {
227 struct PrivateCtx : public Ctx
228 {
229 void* src_user_data = nullptr;
230 std::atomic_bool* is_ready_for_gc = nullptr;
231
232 void ReleaseTaskToPool() const;
233 };
234
235 using JobFn = void (*)(const PrivateCtx& ctx);
236
237#if JOB_SYS_ASSERTIONS
238 void AssertHandler(const bool condition, const char* const filename, const int line_number, const char* const msg);
239#endif
240
241 void DispatchImpl(const char* const name,
242 Counter* const counter,
243 const QueueMode queue,
244 const JobFn func,
245 const std::size_t user_data_size,
246 const std::size_t user_data_alignment,
247 const void* const user_data,
248 void (*InitUserData)(void* const user_data, const void* const in_user_data)) noexcept;
249 }
250
251 template<typename Closure>
252 void Dispatch(const char* const name, Counter* const counter, const Closure& Callback, const QueueMode queue) noexcept
253 {
254 const internal::JobFn ErasedCallback = +[](const internal::PrivateCtx& ctx) -> void {
255 alignas(Closure) unsigned char local_user_data[sizeof(Closure)];
256 Closure* const src_user_data = static_cast<Closure*>(ctx.src_user_data);
257 Closure* const typed_callback = ::new (local_user_data) Closure(std::move(*src_user_data));
258
259 src_user_data->~Closure();
260 ctx.ReleaseTaskToPool();
261
262 (*typed_callback)(static_cast<const job::Ctx&>(ctx));
263 typed_callback->~Closure();
264 };
265
266 internal::DispatchImpl(name, counter, queue, ErasedCallback, sizeof(Closure), alignof(Closure), &Callback, +[](void* const dst_user_data, const void* const src_user_data) -> void { ::new (dst_user_data) Closure(*static_cast<const Closure*>(src_user_data)); });
267 }
268
269 // Parallel Algorithms API
270
271 struct Splitter
272 {
294 static Splitter EvenSplit(const std::size_t total_num_items, std::size_t num_groups_per_thread = 1u)
295 {
296 if (num_groups_per_thread < 1u)
297 {
298 num_groups_per_thread = 1u;
299 }
300
301 return Splitter{(total_num_items / num_groups_per_thread) / NumWorkers()};
302 }
303
304 static constexpr Splitter MaxItemsPerTask(const std::size_t max_items)
305 {
306 return Splitter{max_items};
307 }
308
309 template<typename T>
310 static constexpr Splitter MaxDataSize(const std::size_t max_data_size)
311 {
312 return Splitter{max_data_size / sizeof(T)};
313 }
314
315 std::size_t max_count = 0u;
316
317 constexpr bool operator()(const std::size_t count) const { return count > max_count; }
318 };
319
352 template<typename F, typename S>
353 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)
354 {
355 job::Dispatch(name, counter, [=, splitter = std::forward<S>(splitter), fn = std::forward<F>(fn)](const job::Ctx& ctx) -> void {
356 if (count > 1u && splitter(count))
357 {
358 const std::size_t left_count = count / 2;
359 const std::size_t right_count = count - left_count;
360
361 job::ParallelFor(ctx.task_name, ctx.task_counter, start + 0, left_count, splitter, fn, queue);
362 job::ParallelFor(ctx.task_name, ctx.task_counter, start + left_count, right_count, splitter, fn, queue);
363 }
364 else
365 {
366 job::Ctx child_ctx{ctx};
367 for (std::size_t offset = 0u; offset < count; ++offset)
368 {
369 child_ctx.index = start + offset;
370 fn(child_ctx);
371 }
372 } }, queue);
373 }
374
375 template<typename T, typename F, typename S>
376 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)
377 {
378 return job::ParallelFor(name, counter, std::size_t(0), count, std::forward<S>(splitter), [=](const job::Ctx& ctx) { fn(ctx, data + ctx.index); }, queue);
379 }
380
398 template<typename... F>
399 void ParallelInvoke(const char* const name, Counter* const counter, const QueueMode queue, F&&... fns)
400 {
401 (job::Dispatch(name, counter, std::forward<F>(fns), queue), ...);
402 }
403
404 template<typename Splitter, typename Reducer>
405 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)
406 {
407 const auto ParallelReduce_Impl = [=, splitter = std::forward<Splitter>(splitter), reduce = std::forward<Reducer>(reduce)](const job::Ctx& ctx) -> void {
408 // NOTE(SR):
409 // Could also have a stride that increases each step.
410 // This would be bad for Cuda GPU (Shared Memory Bank Conflict)
411 // But good on CPU with better locality.
412 // https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf
413
414 std::size_t count_left = count;
415
416 while (count_left > 1)
417 {
418 const std::size_t stride = count_left / 2;
419
420 const auto ReduceRange = [stride, &reduce](const job::Ctx& ctx, const std::size_t index) -> void {
421 reduce(ctx, index, index + stride);
422 };
423
424 Counter c{};
425 ParallelFor(name, &c, start, stride, splitter, ReduceRange, queue);
426 WaitOn(c);
427
428 if ((count_left & 1) != 0)
429 {
430 reduce(ctx, start, start + count_left - 1);
431 }
432
433 count_left = stride;
434 }
435 };
436
437 job::Dispatch(name, counter, ParallelReduce_Impl, queue);
438 }
439}
440
441#endif // JOB_API_HPP
442
443/******************************************************************************/
444/*
445 MIT License
446
447 Copyright (c) 2020-2026 Shareef Abdoul-Raheem
448
449 Permission is hereby granted, free of charge, to any person obtaining a copy
450 of this software and associated documentation files (the "Software"), to deal
451 in the Software without restriction, including without limitation the rights
452 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
453 copies of the Software, and to permit persons to whom the Software is
454 furnished to do so, subject to the following conditions:
455
456 The above copyright notice and this permission notice shall be included in all
457 copies or substantial portions of the Software.
458
459 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
460 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
461 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
462 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
463 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
464 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
465 SOFTWARE.
466*/
467/******************************************************************************/
void(*)(const PrivateCtx &ctx) JobFn
Definition: job_api.hpp:235
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:399
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:405
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:353
QueueMode
Determines which threads the task will be allowed to run on.
Definition: job_api.hpp:66
@ 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:252
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:88
std::size_t index
Index for ParallelFor jobs, always zero for regular Dispatch.
Definition: job_api.hpp:58
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:100
std::size_t byte_size
The number of bytes the job system needed.
Definition: job_api.hpp:102
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:103
JobSystemCreateOptions options
The options used to create the memory requirements.
Definition: job_api.hpp:101
static constexpr Splitter MaxItemsPerTask(const std::size_t max_items)
Definition: job_api.hpp:304
std::size_t max_count
Definition: job_api.hpp:315
constexpr bool operator()(const std::size_t count) const
Definition: job_api.hpp:317
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:294
static constexpr Splitter MaxDataSize(const std::size_t max_data_size)
Definition: job_api.hpp:310