Nebula
Loading...
Searching...
No Matches
jobs2.h
Go to the documentation of this file.
1#pragma once
2#include "threading/thread.h"
3#include "threading/event.h"
4#include "util/stringatom.h"
6
7//------------------------------------------------------------------------------
14//------------------------------------------------------------------------------
15namespace Threading
16{
17class Event;
18
19}
20
21namespace Jobs2
22{
23
24#define JOB_SIGNATURE (SizeT totalJobs, SizeT groupSize, IndexT groupIndex, SizeT invocationOffset)
25#define JOB_BEGIN_LOOP \
26for (IndexT i = 0; i < groupSize; i++)\
27{\
28 IndexT __INDEX = i + invocationOffset;\
29 if (__INDEX >= totalJobs)\
30 return;\
31
32#define JOB_END_LOOP \
33}
34
35#define JOB_ITEM_INDEX __INDEX
36
37
38class JobThread;
39
40void* JobAlloc(SizeT bytes);
41typedef volatile long CompletionCounter;
42using JobFunc = void(*)(SizeT totalJobs, SizeT groupSize, IndexT groupIndex, SizeT invocationOffset, void* ctx);
43
44template <typename... ARGS>
46{
47 virtual ~CallableStub() = default;
48 virtual void invoke(ARGS... args) = 0;
49};
50
51template<typename LAMBDA, typename... ARGS>
52struct Callable : CallableStub<ARGS...>
53{
54 LAMBDA l;
55
56 Callable(LAMBDA l) : l(std::move(l)) {};
57
58 void invoke(ARGS... args) override
59 {
60 l(args...);
61 }
62};
63
64struct Lambda
65{
68
69 template<typename LAMBDA>
70 Lambda(LAMBDA l)
71 {
73 this->callable = (CallableType*)Jobs2::JobAlloc(sizeof(CallableType));
74 new (this->callable) CallableType(l);
75 };
76
77 void operator()(SizeT totalJobs, SizeT groupSize, IndexT groupIndex, SizeT invocationOffset)
78 {
79 callable->invoke(totalJobs, groupSize, groupIndex, invocationOffset);
80 }
81
83 void Destroy()
84 {
85 this->callable->~Stub();
86 this->callable = nullptr;
87 }
88};
89
104
106{
109 JobNode* sequence; // set to nullptr for ordinary nodes
110};
111
126
127extern Jobs2Context ctx;
128
129class JobThread : public Threading::Thread
130{
132public:
133
135 JobThread();
137 virtual ~JobThread();
138
140 void SignalWorkAvailable();
141
144protected:
145
147 virtual void EmitWakeupSignal() override;
149 virtual void DoWork() override;
150
151private:
152 Threading::Event wakeupEvent;
153};
154
178
180void JobSystemInit(const JobSystemInitInfo& info);
182void JobSystemUninit();
183
185template <typename T> T* JobAlloc(SizeT count);
187void* JobAlloc(SizeT bytes);
189void JobNewFrame();
190
191extern JobNode* sequenceNode;
192extern JobNode* sequenceTail;
195
198 , Threading::Interlocked::AtomicCounter* doneCounter = nullptr
199 , Threading::Event* signalEvent = nullptr);
200
202template <typename CTX> void JobAppendSequence(
203 const JobFunc& func
204 , const SizeT numInvocations
205 , const SizeT groupSize
206 , const CTX& context
207);
208
210template <typename CTX> void JobAppendSequence(
211 const JobFunc& func
212 , const SizeT numInvocations
213 , const CTX& context
214);
215
217template <typename LAMBDA> void JobAppendSequence(
218 LAMBDA&& func
219 , const SizeT numInvocations
220 , const SizeT groupSize
221);
222
223template <typename LAMBDA> void JobAppendSequence(
224 LAMBDA&& func
225 , const SizeT numInvocations
226);
227
229void JobEndSequence(Threading::Event* signalEvent = nullptr);
230
231//------------------------------------------------------------------------------
234template <typename T> T*
236{
237 return (T*)JobAlloc(count * sizeof(T));
238}
239
240//------------------------------------------------------------------------------
243template <typename LAMBDA> void
245 LAMBDA&& func
246 , const SizeT numInvocations
247 , const SizeT groupSize
249 , Threading::Interlocked::AtomicCounter* doneCounter = nullptr
250 , Threading::Event* signalEvent = nullptr
251)
252{
253 if (numInvocations == 0)
254 {
255 if (doneCounter != nullptr)
256 {
257 doneCounter->Exchange(0);
258 }
259 // If we have a signal event and no invocations, just signal the event and return
260 if (signalEvent != nullptr)
261 {
262 signalEvent->Signal();
263 }
264 return;
265 }
266 n_assert(doneCounter != nullptr ? *doneCounter > 0 : true);
267
268 // Calculate the number of actual jobs based on invocations and group size
269 SizeT numJobs = Math::ceil(numInvocations / float(groupSize));
270
271 // Calculate allocation size which is node + counters + data context
272 SizeT dynamicAllocSize = sizeof(JobNode) + waitCounters.Size() * sizeof(const Threading::Interlocked::AtomicCounter*);
273 auto mem = JobAlloc<char>(dynamicAllocSize);
274 auto node = (JobNode*)mem;
275
276 // Copy over wait counters
277 node->job.waitCounters = nullptr;
278 if (waitCounters.Size() > 0)
279 {
280 node->job.waitCounters = (const Threading::Interlocked::AtomicCounter**)(mem + sizeof(JobNode));
281 memcpy(node->job.waitCounters, waitCounters.Begin(), waitCounters.Size() * sizeof(const Threading::Interlocked::AtomicCounter*));
282 }
283
284 node->job.l = std::move(func);
285 node->job.func = nullptr;
286 node->job.remainingGroups = numJobs;
287 node->job.groupCompletionCounter = numJobs;
288 node->job.numInvocations = numInvocations;
289 node->job.groupSize = groupSize;
290 node->job.numWaitCounters = (SizeT)waitCounters.Size();
291 node->job.doneCounter = doneCounter;
292 node->job.signalEvent = signalEvent;
293 node->sequence = nullptr;
294
295 // Add to end of linked list
296 ctx.jobLock.Enter();
297
298 // First, set head node if nullptr
299 if (ctx.head == nullptr)
300 ctx.head = node;
301
302 // Then add node to end of list
303 node->next = nullptr;
304 if (ctx.tail != nullptr)
305 ctx.tail->next = node;
306 ctx.tail = node;
307
308 ctx.jobLock.Leave();
309
310 // Trigger threads to wake up and compete for jobs
311 for (Ptr<JobThread>& thread : ctx.threads)
312 {
313 thread->SignalWorkAvailable();
314 }
315}
316
317//------------------------------------------------------------------------------
320template <typename LAMBDA> void
322 LAMBDA&& func
323 , const SizeT numInvocations
325 , Threading::Interlocked::AtomicCounter* doneCounter = nullptr
326 , Threading::Event* signalEvent = nullptr
327)
328{
329 JobDispatch(func, numInvocations, numInvocations, waitCounters, doneCounter, signalEvent);
330}
331
332//------------------------------------------------------------------------------
335template <typename CTX> void
337 const JobFunc& func
338 , const SizeT numInvocations
339 , const SizeT groupSize
340 , const CTX& context
342 , Threading::Interlocked::AtomicCounter* doneCounter = nullptr
343 , Threading::Event* signalEvent = nullptr
344)
345{
346 static_assert(std::is_trivially_destructible<CTX>::value, "Job context has to be trivially destructible");
347 n_assert(numInvocations > 0);
348 n_assert(doneCounter != nullptr ? *doneCounter > 0 : true);
349
350 // Calculate the number of actual jobs based on invocations and group size
351 SizeT numJobs = Math::ceil(numInvocations / float(groupSize));
352
353 // Calculate allocation size which is node + counters + data context
354 auto dynamicAllocSize = sizeof(JobNode) + sizeof(CTX) + waitCounters.Size() * sizeof(const Threading::Interlocked::AtomicCounter*);
355 auto mem = JobAlloc<char>(dynamicAllocSize);
356 auto node = (JobNode*)mem;
357
358 // Copy over wait counters
359 node->job.waitCounters = nullptr;
360 if (waitCounters.Size() > 0)
361 {
362 node->job.waitCounters = (const Threading::Interlocked::AtomicCounter**)(mem + sizeof(JobNode) + sizeof(CTX));
363 memcpy(node->job.waitCounters, waitCounters.Begin(), waitCounters.Size() * sizeof(const Threading::Interlocked::AtomicCounter*));
364 }
365
366 // Move context
367 node->job.data = (void*)(mem + sizeof(JobNode));
368 auto data = reinterpret_cast<CTX*>(node->job.data);
369 *data = context;
370
371 node->job.l.callable = nullptr;
372 node->job.func = func;
373 node->job.remainingGroups = numJobs;
374 node->job.groupCompletionCounter = numJobs;
375 node->job.numInvocations = numInvocations;
376 node->job.groupSize = groupSize;
377 node->job.numWaitCounters = (SizeT)waitCounters.Size();
378 node->job.doneCounter = doneCounter;
379 node->job.signalEvent = signalEvent;
380 node->sequence = nullptr;
381
382 // Add to end of linked list
383 ctx.jobLock.Enter();
384
385 // First, set head node if nullptr
386 if (ctx.head == nullptr)
387 ctx.head = node;
388
389 // Then add node to end of list
390 node->next = nullptr;
391 if (ctx.tail != nullptr)
392 ctx.tail->next = node;
393 ctx.tail = node;
394
395 ctx.jobLock.Leave();
396
397 // Trigger threads to wake up and compete for jobs
398 for (Ptr<JobThread>& thread : ctx.threads)
399 {
400 thread->SignalWorkAvailable();
401 }
402}
403
404//------------------------------------------------------------------------------
407template <typename CTX> void
409 const JobFunc& func
410 , const SizeT numInvocations
411 , const CTX& context
413 , Threading::Interlocked::AtomicCounter* doneCounter = nullptr
414 , Threading::Event* signalEvent = nullptr
415)
416{
417 JobDispatch(func, numInvocations, numInvocations, context, waitCounters, doneCounter, signalEvent);
418}
419
420//------------------------------------------------------------------------------
423template<typename CTX> void
424JobAppendSequence(const JobFunc& func, const SizeT numInvocations, const SizeT groupSize, const CTX& context)
425{
426 static_assert(std::is_trivially_destructible<CTX>::value, "Job context has to be trivially destructible");
427 n_assert(numInvocations > 0);
428 n_assert(sequenceThread == Threading::Thread::GetMyThreadId());
429 n_assert(sequenceNode != nullptr);
430
431 // Calculate the number of actual jobs based on invocations and group size
432 SizeT numJobs = Math::ceil(numInvocations / float(groupSize));
433
434 // Calculate allocation size which is node + counters + data context
435 SizeT dynamicAllocSize = sizeof(JobNode) + sizeof(CTX) + sizeof(Threading::Interlocked::AtomicCounter);
436 if (prevDoneCounter != nullptr)
437 dynamicAllocSize += sizeof(Threading::Interlocked::AtomicCounter*);
438 auto mem = JobAlloc<char>(dynamicAllocSize);
439 auto node = (JobNode*)mem;
440
441 if (prevDoneCounter != nullptr)
442 {
443 node->job.numWaitCounters = 1;
444 node->job.waitCounters = (const Threading::Interlocked::AtomicCounter**)(mem + sizeof(JobNode) + sizeof(CTX) + sizeof(Threading::Interlocked::AtomicCounter));
445 memcpy(node->job.waitCounters, &prevDoneCounter, sizeof(Threading::Interlocked::AtomicCounter*));
446 }
447 else
448 {
449 node->job.numWaitCounters = 0;
450 node->job.waitCounters = nullptr;
451 }
452
453 // Move context
454 node->job.data = (void*)(mem + sizeof(JobNode));
455 auto data = reinterpret_cast<CTX*>(node->job.data);
456 *data = context;
457
458 node->job.l.callable = nullptr;
459 node->job.func = func;
460 node->job.remainingGroups = numJobs;
461 node->job.groupCompletionCounter = numJobs;
462 node->job.numInvocations = numInvocations;
463 node->job.groupSize = groupSize;
464
465 node->job.doneCounter = (Threading::Interlocked::AtomicCounter*)(mem + sizeof(JobNode) + sizeof(CTX));
466 *node->job.doneCounter = 1;
467 prevDoneCounter = node->job.doneCounter;
468 node->job.signalEvent = nullptr;
469 node->next = nullptr;
470
471 // The remainingGroups counter for the sequence node is the length of the sequence chain
472 if (sequenceTail == nullptr)
473 sequenceNode->sequence = node;
474 else
475 sequenceTail->next = node;
476
477 sequenceTail = node;
478
479 // Queue job
480 //ctx.queuedJobs.Append(node);
481}
482
483//------------------------------------------------------------------------------
486template<typename CTX> void
487JobAppendSequence(const JobFunc& func, const SizeT numInvocations, const CTX& context)
488{
489 JobAppendSequence(func, numInvocations, numInvocations, context);
490}
491
492//------------------------------------------------------------------------------
495template<typename LAMBDA> void
497 LAMBDA&& func,
498 const SizeT numInvocations,
499 const SizeT groupSize
500)
501{
502 n_assert(numInvocations > 0);
503 n_assert(sequenceThread == Threading::Thread::GetMyThreadId());
504 n_assert(sequenceNode != nullptr);
505
506 // Calculate the number of actual jobs based on invocations and group size
507 SizeT numJobs = Math::ceil(numInvocations / float(groupSize));
508
509 /*
510 // Calculate allocation size which is node + counters + data context
511 SizeT dynamicAllocSize = sizeof(JobNode) + waitCounters.Size() * sizeof(const Threading::AtomicCounter*);
512 auto mem = JobAlloc<char>(dynamicAllocSize);
513 auto node = (JobNode*)mem;
514
515 // Copy over wait counters
516 node->job.waitCounters = nullptr;
517 if (waitCounters.Size() > 0)
518 {
519 node->job.waitCounters = (const Threading::AtomicCounter**)(mem + sizeof(JobNode));
520 memcpy(node->job.waitCounters, waitCounters.Begin(), waitCounters.Size() * sizeof(const Threading::AtomicCounter*));
521 }
522 */
523
524 // Calculate allocation size which is node + counters + data context
525 SizeT dynamicAllocSize = sizeof(JobNode) + sizeof(Threading::Interlocked::AtomicCounter);
526 if (prevDoneCounter != nullptr)
527 dynamicAllocSize += sizeof(Threading::Interlocked::AtomicCounter*);
528 auto mem = JobAlloc<char>(dynamicAllocSize);
529 auto node = (JobNode*)mem;
530
531 if (prevDoneCounter != nullptr)
532 {
533 node->job.numWaitCounters = 1;
534 node->job.waitCounters = (const Threading::Interlocked::AtomicCounter**)(mem + sizeof(JobNode) + sizeof(Threading::Interlocked::AtomicCounter));
535 memcpy(node->job.waitCounters, &prevDoneCounter, sizeof(Threading::Interlocked::AtomicCounter*));
536 }
537 else
538 {
539 node->job.numWaitCounters = 0;
540 node->job.waitCounters = nullptr;
541 }
542
543 // Move context
544 node->job.l = std::move(func);
545 node->job.func = nullptr;
546 node->job.remainingGroups = numJobs;
547 node->job.groupCompletionCounter = numJobs;
548 node->job.numInvocations = numInvocations;
549 node->job.groupSize = groupSize;
550
551 node->job.doneCounter = (Threading::Interlocked::AtomicCounter*)(mem + sizeof(JobNode));
552 *node->job.doneCounter = 1;
553 prevDoneCounter = node->job.doneCounter;
554 node->job.signalEvent = nullptr;
555 node->next = nullptr;
556
557 // The remainingGroups counter for the sequence node is the length of the sequence chain
558 if (sequenceTail == nullptr)
559 sequenceNode->sequence = node;
560 else
561 sequenceTail->next = node;
562
563 sequenceTail = node;
564
565 // Queue job
566 //ctx.queuedJobs.Append(node);
567}
568
569//------------------------------------------------------------------------------
572template<typename LAMBDA> void
573JobAppendSequence(LAMBDA&& func, const SizeT numInvocations)
574{
575 JobAppendSequence(func, numInvocations, numInvocations);
576}
577
578} // namespace Jobs2
JobThread()
constructor
Definition jobs2.cc:19
virtual void DoWork() override
this method runs in the thread context
Definition jobs2.cc:58
Threading::Event wakeupEvent
Definition jobs2.h:152
void SignalWorkAvailable()
Signal new work available.
Definition jobs2.cc:40
__DeclareClass(JobThread)
virtual void EmitWakeupSignal() override
override this method if your thread loop needs a wakeup call before stopping
Definition jobs2.cc:49
bool enableProfiling
Definition jobs2.h:143
bool enableIo
Definition jobs2.h:142
Nebula's smart pointer class which manages the life time of RefCounted objects.
Definition ptr.h:38
Nebula's dynamic array class.
Definition array.h:61
Implements a fixed size one-dimensional array.
Definition fixedarray.h:20
A StringAtom.
Definition stringatom.h:22
#define n_assert(exp)
Definition debug.h:58
Definition jobs2.cc:11
const Threading::Interlocked::AtomicCounter * prevDoneCounter
Definition jobs2.cc:301
void JobBeginSequence(const Util::FixedArray< const Threading::Interlocked::AtomicCounter *, true > &waitCounters, Threading::Interlocked::AtomicCounter *doneCounter, Threading::Event *signalEvent)
Begin a sequence of jobs.
Definition jobs2.cc:308
Threading::ThreadId sequenceThread
Definition jobs2.cc:302
Jobs2Context ctx
Definition jobs2.cc:13
Jobs2::JobNode * sequenceNode
Definition jobs2.cc:299
Jobs2::JobNode * sequenceTail
Definition jobs2.cc:300
void JobEndSequence(Threading::Event *signalEvent)
Flush queued jobs.
Definition jobs2.cc:349
void(*)(SizeT totalJobs, SizeT groupSize, IndexT groupIndex, SizeT invocationOffset, void *ctx) JobFunc
Definition jobs2.h:42
void JobSystemUninit()
Destroy job port.
Definition jobs2.cc:245
void JobDispatch(LAMBDA &&func, const SizeT numInvocations, const SizeT groupSize, const Util::FixedArray< const Threading::Interlocked::AtomicCounter *, true > &waitCounters=nullptr, Threading::Interlocked::AtomicCounter *doneCounter=nullptr, Threading::Event *signalEvent=nullptr)
Definition jobs2.h:244
void * JobAlloc(SizeT bytes)
Allocate memory.
Definition jobs2.cc:269
void JobSystemInit(const JobSystemInitInfo &info)
Create a new job port.
Definition jobs2.cc:212
volatile long CompletionCounter
Definition jobs2.h:41
void JobNewFrame()
Progress to new buffer.
Definition jobs2.cc:258
void JobAppendSequence(const JobFunc &func, const SizeT numInvocations, const SizeT groupSize, const CTX &context)
Append job to sequence with an automatic dependency on the previous job.
Definition jobs2.h:424
__forceinline float ceil(float val)
Floating point ceiling.
Definition scalar.h:542
The Jobs2 system provides a set of threads and a pool of jobs from which threads can pickup work.
Definition jobs2.h:16
pthread_t ThreadId
Definition linuxthreadid.h:15
Definition half.h:491
Definition jobs2.h:53
LAMBDA l
Definition jobs2.h:54
void invoke(ARGS... args) override
Definition jobs2.h:58
Callable(LAMBDA l)
Definition jobs2.h:56
Definition jobs2.h:46
virtual ~CallableStub()=default
virtual void invoke(ARGS... args)=0
Definition jobs2.h:91
const Threading::Interlocked::AtomicCounter ** waitCounters
Definition jobs2.h:99
void * data
Definition jobs2.h:98
SizeT groupSize
Definition jobs2.h:97
Threading::Interlocked::AtomicCounter * doneCounter
Definition jobs2.h:101
int remainingGroups
Definition jobs2.h:94
SizeT numWaitCounters
Definition jobs2.h:100
Threading::Interlocked::AtomicCounter groupCompletionCounter
Definition jobs2.h:95
SizeT numInvocations
Definition jobs2.h:96
Threading::Event * signalEvent
Definition jobs2.h:102
JobFunc func
Definition jobs2.h:92
Lambda l
Definition jobs2.h:93
Definition jobs2.h:106
JobNode * next
Definition jobs2.h:107
JobContext job
Definition jobs2.h:108
JobNode * sequence
Definition jobs2.h:109
uint priority
Definition jobs2.h:160
bool enableIo
Definition jobs2.h:165
SizeT scratchMemorySize
Definition jobs2.h:162
Util::StringAtom name
Definition jobs2.h:157
uint affinity
Definition jobs2.h:159
JobSystemInitInfo()
Definition jobs2.h:168
SizeT numThreads
Definition jobs2.h:158
bool enableProfiling
Definition jobs2.h:166
SizeT numBuffers
Definition jobs2.h:163
Definition jobs2.h:113
IndexT activeBuffer
Definition jobs2.h:122
JobNode * head
Definition jobs2.h:115
Util::Array< JobNode * > queuedJobs
Definition jobs2.h:118
SizeT scratchMemorySize
Definition jobs2.h:124
IndexT iterator
Definition jobs2.h:121
SizeT numBuffers
Definition jobs2.h:120
Util::FixedArray< byte * > scratchMemory
Definition jobs2.h:123
JobNode * tail
Definition jobs2.h:116
Util::FixedArray< Ptr< JobThread > > threads
Definition jobs2.h:117
Threading::CriticalSection jobLock
Definition jobs2.h:114
Definition jobs2.h:65
void Destroy()
destroy the captured state, the memory itself belongs to the job scratch buffer
Definition jobs2.h:83
Lambda(LAMBDA l)
Definition jobs2.h:70
Stub * callable
Definition jobs2.h:67
void operator()(SizeT totalJobs, SizeT groupSize, IndexT groupIndex, SizeT invocationOffset)
Definition jobs2.h:77
CallableStub< SizeT, SizeT, IndexT, SizeT > Stub
Definition jobs2.h:66
Atomic value only used to decrement, increment and read a 32 bit value.
Definition interlocked.h:214
int SizeT
Definition types.h:42
unsigned int uint
Definition types.h:33
int IndexT
Definition types.h:41