├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── License.txt ├── example ├── Timer.h ├── LambdaTask.cpp ├── PinnedTask_c.c ├── PinnedTask.cpp ├── Priorities.cpp ├── Priorities_c.c ├── ExternalTaskThread.cpp ├── CustomAllocator.cpp ├── CustomAllocator_c.c ├── ExternalTaskThread_c.c ├── WaitForNewPinnedTasks_c.c ├── Dependencies.cpp ├── TestWaitforTask.cpp ├── ParallelSum_c.c ├── TaskThroughput.cpp ├── Dependencies_c.c ├── WaitForNewPinnedTasks.cpp ├── ParallelSum.cpp ├── TaskOverhead.cpp ├── CompletionAction.cpp ├── CompletionAction_c.c └── TestAll.cpp ├── CMakeLists.txt ├── src ├── LockLessMultiReadPipe.h ├── TaskScheduler_c.cpp └── TaskScheduler_c.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dougbinks 4 | patreon: enkisoftware 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.enkisoftware.com/ 13 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Doug Binks 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgement in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /example/Timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // simple time class you shouldn't use outside example code. 6 | class Timer 7 | { 8 | double timeMS = 0.0; 9 | bool bRunning = false; 10 | public: 11 | Timer() = default; 12 | 13 | // start does not reset time to 0, use reset 14 | void Start() 15 | { 16 | bRunning = true; 17 | start = std::chrono::high_resolution_clock::now(); 18 | } 19 | 20 | double GetTimeMS() 21 | { 22 | if( !bRunning ) 23 | { 24 | return timeMS; 25 | } 26 | auto stop = std::chrono::high_resolution_clock::now(); 27 | return 1000.0 * std::chrono::duration_cast>(stop - start).count(); 28 | }; 29 | 30 | void Stop() 31 | { 32 | if( bRunning ) 33 | { 34 | timeMS += GetTimeMS(); 35 | bRunning = false; 36 | } 37 | } 38 | 39 | void Reset() 40 | { 41 | timeMS = 0.0; 42 | bRunning = false; 43 | } 44 | 45 | private: 46 | std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); 47 | }; 48 | -------------------------------------------------------------------------------- /example/LambdaTask.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | #include 24 | 25 | #ifndef _WIN32 26 | #include 27 | #endif 28 | 29 | 30 | // lambda example 31 | 32 | int main(int argc, const char * argv[]) 33 | { 34 | enki::TaskScheduler g_TS; 35 | g_TS.Initialize(); 36 | 37 | enki::TaskSet task( 1024, []( enki::TaskSetPartition range, uint32_t threadnum ) { printf("Thread %d, start %d, end %d\n", threadnum, range.start, range.end ); } ); 38 | 39 | g_TS.AddTaskSetToPipe( &task ); 40 | g_TS.WaitforTask( &task ); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /example/PinnedTask_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | 24 | enkiTaskScheduler* pETS; 25 | enkiTaskSet* pParallelTask; 26 | enkiPinnedTask* pPinnedTask; 27 | 28 | static const int REPEATS = 5; 29 | 30 | void PinnedTaskFunc( void* pArgs_ ) 31 | { 32 | printf("This will run on the main thread\n"); 33 | } 34 | 35 | void ParallelTaskSetFunc( uint32_t start_, uint32_t end, uint32_t threadnum_, void* pArgs_ ) 36 | { 37 | enkiAddPinnedTask( pETS, pPinnedTask ); 38 | printf("This could run on any thread, currently thread %d\n", threadnum_); 39 | enkiWaitForPinnedTask( pETS, pPinnedTask ); 40 | } 41 | 42 | int main(int argc, const char * argv[]) 43 | { 44 | int run; 45 | pETS = enkiNewTaskScheduler(); 46 | enkiInitTaskScheduler( pETS ); 47 | 48 | pParallelTask = enkiCreateTaskSet( pETS, ParallelTaskSetFunc ); 49 | pPinnedTask = enkiCreatePinnedTask( pETS, PinnedTaskFunc, 0 ); // pinned task is created for thread 0 50 | 51 | 52 | for( run = 0; run< REPEATS; ++run ) 53 | { 54 | enkiAddTaskSet( pETS, pParallelTask ); 55 | 56 | enkiRunPinnedTasks( pETS ); 57 | 58 | enkiWaitForTaskSet( pETS, pParallelTask ); 59 | } 60 | 61 | enkiDeleteTaskSet( pETS, pParallelTask ); 62 | enkiDeletePinnedTask( pETS, pPinnedTask ); 63 | 64 | enkiDeleteTaskScheduler( pETS ); 65 | } 66 | -------------------------------------------------------------------------------- /example/PinnedTask.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | 21 | #include 22 | 23 | using namespace enki; 24 | 25 | TaskScheduler g_TS; 26 | 27 | 28 | struct PinnedTaskHelloWorld : IPinnedTask 29 | { 30 | PinnedTaskHelloWorld() 31 | : IPinnedTask(0) // set pinned thread to 0 32 | {} 33 | void Execute() override 34 | { 35 | printf("This will run on the main thread\n"); 36 | } 37 | }; 38 | 39 | struct ParallelTaskSet : ITaskSet 40 | { 41 | PinnedTaskHelloWorld pinnedTask; 42 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 43 | { 44 | g_TS.AddPinnedTask( &pinnedTask ); 45 | 46 | printf("This could run on any thread, currently thread %d\n", threadnum_); 47 | 48 | g_TS.WaitforTask( &pinnedTask ); 49 | } 50 | }; 51 | 52 | static const int REPEATS = 5; 53 | 54 | int main(int argc, const char * argv[]) 55 | { 56 | g_TS.Initialize(); 57 | 58 | for( int run = 0; run< REPEATS; ++run ) 59 | { 60 | 61 | ParallelTaskSet task; 62 | g_TS.AddTaskSetToPipe( &task ); 63 | 64 | printf("Task %d added\n", run ); 65 | 66 | // RunPinnedTasks must be called on main thread to run any pinned tasks for that thread. 67 | // Tasking threads automatically do this in their task loop. 68 | g_TS.RunPinnedTasks(); 69 | 70 | g_TS.WaitforTask( &task); 71 | } 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /example/Priorities.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | 24 | enki::TaskScheduler g_TS; 25 | 26 | struct ExampleTask : enki::ITaskSet 27 | { 28 | ExampleTask( uint32_t size_ ) { m_SetSize = size_; } 29 | 30 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override 31 | { 32 | if( m_Priority == enki::TASK_PRIORITY_LOW ) 33 | { 34 | // fake slow task with timer 35 | Timer timer; 36 | timer.Start(); 37 | double tWaittime = (double)( range_.end - range_.start ) * 100.; 38 | while( timer.GetTimeMS() < tWaittime ) 39 | { 40 | } 41 | printf( "\tLOW PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n", 42 | threadnum_, range_.start, range_.end ); 43 | } 44 | else 45 | { 46 | printf( "HIGH PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n", 47 | threadnum_, range_.start, range_.end ); 48 | } 49 | } 50 | }; 51 | 52 | 53 | // This example demonstrates how to run a long running task alongside tasks 54 | // which must complete as early as possible using priorities. 55 | int main(int argc, const char * argv[]) 56 | { 57 | g_TS.Initialize(); 58 | 59 | ExampleTask lowPriorityTask( 10 ); 60 | lowPriorityTask.m_Priority = enki::TASK_PRIORITY_LOW; 61 | 62 | ExampleTask highPriorityTask( 1 ); 63 | highPriorityTask.m_Priority = enki::TASK_PRIORITY_HIGH; 64 | 65 | g_TS.AddTaskSetToPipe( &lowPriorityTask ); 66 | for( int task = 0; task < 10; ++task ) 67 | { 68 | // run high priority tasks 69 | g_TS.AddTaskSetToPipe( &highPriorityTask ); 70 | 71 | // wait for task but only run tasks of the same priority on this thread 72 | g_TS.WaitforTask( &highPriorityTask, highPriorityTask.m_Priority ); 73 | } 74 | // wait for low priority task, run any tasks on this thread whilst waiting 75 | g_TS.WaitforTask( &lowPriorityTask ); 76 | 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /example/Priorities_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | void LowPriorityTask( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ) 28 | { 29 | int wait = (int)( 0.1f * (float)( ( end_ - start_ ) * (float)CLOCKS_PER_SEC) ); 30 | clock_t endTime = (clock_t)wait + clock(); 31 | while( clock() < endTime ) 32 | { 33 | } 34 | printf( "\tLOW PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n", 35 | threadnum_, start_, end_ ); 36 | } 37 | 38 | void HighPriorityTask( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ) 39 | { 40 | printf( "HIGH PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n", 41 | threadnum_, start_, end_ ); 42 | } 43 | 44 | int main(int argc, const char * argv[]) 45 | { 46 | int run; 47 | enkiTaskScheduler* pETS; 48 | enkiTaskSet* plowPriorityTask; 49 | enkiTaskSet* pHighPriorityTask; 50 | 51 | pETS = enkiNewTaskScheduler(); 52 | enkiInitTaskScheduler( pETS ); 53 | 54 | plowPriorityTask = enkiCreateTaskSet( pETS, LowPriorityTask ); 55 | enkiSetPriorityTaskSet( plowPriorityTask, 1 ); // lower values higher priority 56 | enkiSetSetSizeTaskSet( plowPriorityTask, 10 ); 57 | 58 | pHighPriorityTask = enkiCreateTaskSet( pETS, HighPriorityTask ); 59 | enkiSetPriorityTaskSet( pHighPriorityTask, 0 ); // lower values higher priority 60 | 61 | enkiAddTaskSet( pETS, plowPriorityTask ); 62 | for( run = 0; run < 10; ++run ) 63 | { 64 | // run high priority tasks 65 | enkiAddTaskSet( pETS, pHighPriorityTask ); 66 | 67 | // wait for task but only run tasks of the same priority or higher on this thread 68 | enkiWaitForTaskSetPriority( pETS, pHighPriorityTask, 0 ); 69 | } 70 | 71 | // wait for low priority task, run any tasks on this thread whilst waiting 72 | enkiWaitForTaskSet( pETS, plowPriorityTask ); 73 | 74 | enkiDeleteTaskSet( pETS, plowPriorityTask ); 75 | enkiDeleteTaskSet( pETS, pHighPriorityTask ); 76 | 77 | enkiDeleteTaskScheduler( pETS ); 78 | } 79 | -------------------------------------------------------------------------------- /example/ExternalTaskThread.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | 21 | #include 22 | #include 23 | 24 | using namespace enki; 25 | 26 | TaskScheduler g_TS; 27 | static std::atomic g_Run; 28 | 29 | 30 | struct ParallelTaskSet : ITaskSet 31 | { 32 | ParallelTaskSet() { m_SetSize = 100; } 33 | 34 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 35 | { 36 | printf(" Run %d: This could run on any thread, currently thread %d\n", g_Run.load(), threadnum_); 37 | 38 | // sleep used as a 'pretend' workload 39 | std::chrono::milliseconds sleepTime( range_.end - range_.start ); 40 | std::this_thread::sleep_for( sleepTime ); 41 | } 42 | }; 43 | 44 | // Example thread function 45 | // May want to use threads for blocking IO, during which enkiTS task threads can do work 46 | void threadFunction( uint32_t num_ ) 47 | { 48 | bool bRegistered = g_TS.RegisterExternalTaskThread(); 49 | assert( bRegistered ); 50 | if( bRegistered ) 51 | { 52 | // sleep for a while instead of doing something such as file IO 53 | std::this_thread::sleep_for( std::chrono::milliseconds( num_ * 100 ) ); 54 | 55 | 56 | ParallelTaskSet task; 57 | g_TS.AddTaskSetToPipe( &task ); 58 | g_TS.WaitforTask( &task); 59 | g_TS.DeRegisterExternalTaskThread(); 60 | } 61 | } 62 | 63 | static const int REPEATS = 5; 64 | static const uint32_t NUMEXTERNALTHREADS = 5; 65 | 66 | int main(int argc, const char * argv[]) 67 | { 68 | enki::TaskSchedulerConfig config; 69 | config.numExternalTaskThreads = NUMEXTERNALTHREADS; 70 | 71 | std::thread threads[NUMEXTERNALTHREADS]; 72 | g_TS.Initialize( config ); 73 | 74 | 75 | for( g_Run = 0; g_Run< REPEATS; ++g_Run ) 76 | { 77 | printf("Run %d\n", g_Run.load() ); 78 | 79 | for( uint32_t iThread = 0; iThread < NUMEXTERNALTHREADS; ++iThread ) 80 | { 81 | threads[ iThread ] = std::thread( threadFunction, iThread ); 82 | } 83 | 84 | // check that out of order Deregister / Register works... 85 | threads[ 0 ].join(); 86 | threads[ 0 ] = std::thread( threadFunction, 0 ); 87 | 88 | for( uint32_t iThread = 0; iThread < NUMEXTERNALTHREADS; ++iThread ) 89 | { 90 | threads[ iThread ].join(); 91 | } 92 | } 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /example/CustomAllocator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | 21 | #include 22 | #include 23 | 24 | using namespace enki; 25 | 26 | TaskScheduler g_TS; 27 | 28 | struct ParallelTaskSet : ITaskSet 29 | { 30 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 31 | { 32 | printf(" This could run on any thread, currently thread %d\n", threadnum_); 33 | } 34 | }; 35 | 36 | struct CustomData 37 | { 38 | const char* domainName; 39 | size_t totalAllocations; 40 | }; 41 | 42 | void* CustomAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ ) 43 | { 44 | CustomData* data = (CustomData*)userData_; 45 | data->totalAllocations += size_; 46 | 47 | // We don't need to use this macro as file_ and line_ will be valid and printable just not useful 48 | // But for this example it makes prettier output :) 49 | #ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE 50 | printf("Allocating %g bytes with alignment %g in domain %s, total %g. File %s, line %d.\n", 51 | (double)size_, (double)align_, data->domainName, (double)data->totalAllocations, file_, line_ ); 52 | #else 53 | printf("Allocating %g bytes with alignment %g in domain %s, total %g.\n", 54 | (double)size_, (double)align_, data->domainName, (double)data->totalAllocations ); 55 | #endif 56 | 57 | return DefaultAllocFunc( align_, size_, userData_, file_, line_ ); 58 | }; 59 | 60 | void CustomFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ ) 61 | { 62 | CustomData* data = (CustomData*)userData_; 63 | data->totalAllocations -= size_; 64 | 65 | // we don't need to use this macro as file_ and line_ will be valid and printable just not useful 66 | // But for this example it makes prettier output :) 67 | #ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE 68 | printf("Freeing %p in domain %s, total %g. File %s, line %d.\n", 69 | ptr_, data->domainName, (double)data->totalAllocations, file_, line_ ); 70 | #else 71 | (void)file_; (void)line_; 72 | printf("Freeing %p in domain %s, total %g.\n", 73 | ptr_, data->domainName, (double)data->totalAllocations ); 74 | #endif 75 | 76 | DefaultFreeFunc( ptr_, size_, userData_, file_, line_ ); 77 | }; 78 | 79 | 80 | int main(int argc, const char * argv[]) 81 | { 82 | enki::TaskSchedulerConfig config; 83 | config.customAllocator.alloc = CustomAllocFunc; 84 | config.customAllocator.free = CustomFreeFunc; 85 | CustomData data{ "enkITS", 0 }; 86 | config.customAllocator.userData = &data; 87 | 88 | g_TS.Initialize( config ); 89 | 90 | ParallelTaskSet task; 91 | g_TS.AddTaskSetToPipe( &task ); 92 | g_TS.WaitforTask( &task ); 93 | g_TS.WaitforAllAndShutdown(); // ensure we shutdown before user data is destroyed. 94 | 95 | return 0; 96 | } 97 | -------------------------------------------------------------------------------- /example/CustomAllocator_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | enkiTaskScheduler* pETS; 26 | enkiTaskSet* pParallelTask; 27 | 28 | size_t totalAllocations = 0; 29 | 30 | void ParallelFunc( uint32_t start_, uint32_t end, uint32_t threadnum_, void* pArgs_ ) 31 | { 32 | // do something 33 | printf("ParallelFunc running on thread %d (could be any thread)\n", threadnum_ ); 34 | } 35 | 36 | void* CustomAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ ) 37 | { 38 | totalAllocations += size_; // note this isn't thread safe, should use atomics in real application 39 | 40 | // We don't need to use this macro as file_ and line_ will be valid and printable just not useful 41 | // But for this example it makes prettier output :) 42 | #ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE 43 | printf("Allocating %g bytes in domain %s, total %g. File %s, line %d.\n", 44 | (double)size_, (const char*)userData_, (double)totalAllocations, file_, line_ ); 45 | #else 46 | (void)file_; (void)line_; 47 | printf("Allocating %g bytes in domain %s, total %g.\n", 48 | (double)size_, (const char*)userData_, (double)totalAllocations ); 49 | #endif 50 | 51 | return enkiDefaultAllocFunc( align_, size_, userData_, file_, line_ ); 52 | }; 53 | 54 | void CustomFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ ) 55 | { 56 | totalAllocations -= size_; // note this isn't thread safe, should use atomics in real application 57 | 58 | // We don't need to use this macro as file_ and line_ will be valid and printable just not useful 59 | // But for this example it makes prettier output :) 60 | #ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE 61 | printf("Freeing %p in domain %s, total %g. File %s, line %d.\n", 62 | ptr_, (const char*)userData_, (double)totalAllocations, file_, line_ ); 63 | #else 64 | (void)file_; (void)line_; 65 | printf("Freeing %p in domain %s, total %g.\n", 66 | ptr_, (const char*)userData_, (double)totalAllocations ); 67 | #endif 68 | 69 | enkiDefaultFreeFunc( ptr_, size_, userData_, file_, line_ ); 70 | }; 71 | 72 | 73 | int main(int argc, const char * argv[]) 74 | { 75 | struct enkiCustomAllocator customAllocator; 76 | 77 | customAllocator.alloc = CustomAllocFunc; 78 | customAllocator.free = CustomFreeFunc; 79 | customAllocator.userData = (void*)"enkiTS"; 80 | 81 | pETS = enkiNewTaskSchedulerWithCustomAllocator( customAllocator ); 82 | enkiInitTaskScheduler( pETS ); 83 | 84 | pParallelTask = enkiCreateTaskSet( pETS, ParallelFunc ); 85 | 86 | enkiAddTaskSet( pETS, pParallelTask ); 87 | enkiWaitForTaskSet( pETS, pParallelTask ); 88 | 89 | enkiDeleteTaskSet( pETS, pParallelTask ); 90 | 91 | enkiDeleteTaskScheduler( pETS ); 92 | } 93 | -------------------------------------------------------------------------------- /example/ExternalTaskThread_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | // XPLATF Thread handling functions for C 26 | #ifdef _WIN32 27 | 28 | #define NOMINMAX 29 | #define WIN32_LEAN_AND_MEAN 30 | #include 31 | 32 | typedef HANDLE threadid_t; 33 | #define THREADFUNC_DECL DWORD WINAPI 34 | 35 | // declare the thread start function as: 36 | // THREADFUNC_DECL MyThreadStart( void* pArg ); 37 | int32_t ThreadCreate( threadid_t* returnid, DWORD ( WINAPI *StartFunc) (void* ), void* pArg ) 38 | { 39 | DWORD threadid; 40 | *returnid = CreateThread( 0, 0, StartFunc, pArg, 0, &threadid ); 41 | return *returnid != NULL; 42 | } 43 | 44 | int32_t ThreadJoin( threadid_t threadid ) 45 | { 46 | return WaitForSingleObject( threadid, INFINITE ) == 0; 47 | } 48 | 49 | #else // posix 50 | #include 51 | #include 52 | 53 | typedef pthread_t threadid_t; 54 | #define THREADFUNC_DECL void* 55 | 56 | // declare the thread start function as: 57 | // THREADFUNC_DECL MyThreadStart( void* pArg ); 58 | int32_t ThreadCreate( threadid_t* returnid, void* ( *StartFunc) (void* ), void* pArg ) 59 | { 60 | int32_t retval = pthread_create( returnid, NULL, StartFunc, pArg ); 61 | 62 | return retval == 0; 63 | } 64 | 65 | int32_t ThreadJoin( threadid_t threadid ) 66 | { 67 | return pthread_join( threadid, NULL ) == 0; 68 | } 69 | #endif 70 | 71 | enkiTaskScheduler* pETS; 72 | enkiTaskSet* pParallelTask; 73 | 74 | 75 | void ParallelFunc( uint32_t start_, uint32_t end, uint32_t threadnum_, void* pArgs_ ) 76 | { 77 | // do something 78 | printf("ParallelFunc running on thread %d (could be any thread)\n", threadnum_ ); 79 | } 80 | 81 | THREADFUNC_DECL ThreadFunc( void* pArgs_ ) 82 | { 83 | uint32_t threadNum; 84 | int retVal; 85 | 86 | retVal = enkiRegisterExternalTaskThread( pETS ); 87 | assert( retVal ); 88 | 89 | threadNum = enkiGetThreadNum( pETS ); 90 | assert( threadNum == 1 ); 91 | printf("ThreadFunc running on thread %d (should be thread 1)\n", threadNum ); 92 | 93 | pParallelTask = enkiCreateTaskSet( pETS, ParallelFunc ); 94 | 95 | enkiAddTaskSet( pETS, pParallelTask ); 96 | enkiWaitForTaskSet( pETS, pParallelTask ); 97 | 98 | enkiDeleteTaskSet( pETS, pParallelTask ); 99 | 100 | enkiDeRegisterExternalTaskThread( pETS ); 101 | return 0; 102 | } 103 | 104 | int main(int argc, const char * argv[]) 105 | { 106 | struct enkiTaskSchedulerConfig config; 107 | 108 | pETS = enkiNewTaskScheduler(); 109 | 110 | // get default config and request one external thread 111 | config = enkiGetTaskSchedulerConfig( pETS ); 112 | config.numExternalTaskThreads = 1; 113 | enkiInitTaskSchedulerWithConfig( pETS, config ); 114 | 115 | threadid_t threadID; 116 | ThreadCreate( &threadID, ThreadFunc, NULL ); 117 | 118 | ThreadJoin( threadID ); 119 | 120 | 121 | enkiDeleteTaskScheduler( pETS ); 122 | } 123 | -------------------------------------------------------------------------------- /example/WaitForNewPinnedTasks_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | enkiTaskScheduler* pETS; 26 | 27 | 28 | void PinnedTaskPretendIOFunc( void* pArgs_ ) 29 | { 30 | int32_t dataVal = *(int32_t*)pArgs_; 31 | printf("Run %d: Example PinnedTaskPretendIOFunc - this could perform network or file IO\n", dataVal ); 32 | } 33 | 34 | void PinnedTaskRunPinnedTaskLoop( void* pArgs_ ) 35 | { 36 | uint32_t threadNumDesired = *(uint32_t*)pArgs_; 37 | uint32_t threadNum = enkiGetThreadNum( pETS ); 38 | assert( threadNum == threadNumDesired ); 39 | printf("PinnedTaskRunPinnedTaskLoop running on thread %d (should be thread %d)\n", threadNum, threadNumDesired ); 40 | 41 | while( !enkiGetIsShutdownRequested( pETS ) ) 42 | { 43 | enkiWaitForNewPinnedTasks( pETS ); 44 | enkiRunPinnedTasks( pETS ); 45 | } 46 | } 47 | 48 | int main(int argc, const char * argv[]) 49 | { 50 | struct enkiTaskSchedulerConfig config; 51 | enkiPinnedTask* pPinnedTaskRunPinnedTaskLoop; 52 | enkiPinnedTask* pPinnedTaskPretendIO; 53 | 54 | pETS = enkiNewTaskScheduler(); 55 | 56 | // get default config and request one external thread 57 | config = enkiGetTaskSchedulerConfig( pETS ); 58 | 59 | // In this example we create more threads than the hardware can run, 60 | // because the IO thread will spend most of it's time idle or blocked 61 | // and therefore not scheduled for CPU time by the OS 62 | config.numTaskThreadsToCreate += 1; // Create 1 extra thread for IO tasks (could create more if needed) 63 | enkiInitTaskSchedulerWithConfig( pETS, config ); 64 | 65 | uint32_t threadNumIOTasks = config.numTaskThreadsToCreate; // thread 0 is this thread, so last thread is num threads created. 66 | 67 | // create task to run pinned task loop 68 | pPinnedTaskRunPinnedTaskLoop = enkiCreatePinnedTask( pETS, PinnedTaskRunPinnedTaskLoop, threadNumIOTasks ); 69 | enkiAddPinnedTaskArgs( pETS, pPinnedTaskRunPinnedTaskLoop, &threadNumIOTasks ); 70 | 71 | // send pretend IO commands to external thread 72 | pPinnedTaskPretendIO = enkiCreatePinnedTask( pETS, PinnedTaskPretendIOFunc, threadNumIOTasks ); 73 | for( int32_t i=0; i<5; ++i ) 74 | { 75 | // we re-use one task here as we are waiting for each to complete 76 | enkiAddPinnedTaskArgs( pETS, pPinnedTaskPretendIO, &i ); 77 | 78 | // in most real world cases you would not wait for pinned IO task immediatly after 79 | // issueing it, but instead do work. 80 | // Rather than waiting can use dependencies or issue a pinned task to main thread (id 0) to send data 81 | enkiWaitForPinnedTask( pETS, pPinnedTaskPretendIO ); 82 | } 83 | enkiDeletePinnedTask( pETS, pPinnedTaskPretendIO ); 84 | 85 | 86 | // Shutdown enkiTS, which will cause pPinnedTaskRunPinnedTaskLoop to exit as enkiGetIsShutdownRequested will return true 87 | enkiWaitforAllAndShutdown( pETS ); 88 | 89 | // delete the tasks before the scheduler 90 | enkiDeletePinnedTask( pETS, pPinnedTaskRunPinnedTaskLoop ); 91 | 92 | enkiDeleteTaskScheduler( pETS ); 93 | printf("WaitForNewPinnedTasks_c.c completed\n" ); 94 | 95 | } 96 | -------------------------------------------------------------------------------- /example/Dependencies.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include 28 | #endif 29 | 30 | using namespace enki; 31 | 32 | TaskScheduler g_TS; 33 | 34 | 35 | #include 36 | 37 | // We use a task to launch the first task in the task graph 38 | // as adding the tasks with many dependencies incurs an overhead, 39 | // which we might not want to occur on the main thread 40 | struct TaskLauncher : ITaskSet 41 | { 42 | ITaskSet* m_pTaskToLaunch = NULL; 43 | void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override 44 | { 45 | (void)range; 46 | g_TS.AddTaskSetToPipe( m_pTaskToLaunch ); 47 | } 48 | }; 49 | 50 | struct TaskA : ITaskSet 51 | { 52 | void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override 53 | { 54 | (void)range; 55 | printf("A on thread %u\n", threadnum); 56 | } 57 | }; 58 | 59 | struct TaskB : ITaskSet 60 | { 61 | Dependency m_Dependency; 62 | 63 | void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override 64 | { 65 | (void)range; 66 | printf("B on thread %u\n", threadnum); 67 | } 68 | }; 69 | 70 | struct TaskC : IPinnedTask 71 | { 72 | Dependency m_Dependencies[4]; 73 | 74 | void Execute() override 75 | { 76 | printf("C Pinned task on thread %u, should be %u\n", g_TS.GetThreadNum(), threadNum ); 77 | } 78 | }; 79 | 80 | struct TaskD : ITaskSet 81 | { 82 | Dependency m_Dependency; 83 | 84 | void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override 85 | { 86 | (void)range; 87 | printf("D on thread %u\n", threadnum); 88 | } 89 | }; 90 | 91 | // If you need to wait on multiple dependencies, but don't need to do anything 92 | // you can derive from ICompletable and add depedencies 93 | struct TasksFinished : ICompletable 94 | { 95 | std::vector m_Dependencies; 96 | Dependency m_DepencyOnLauncher; // could also store this in array above 97 | }; 98 | 99 | static const int RUNS = 20; 100 | 101 | int main(int argc, const char * argv[]) 102 | { 103 | g_TS.Initialize(); 104 | 105 | // construct the graph once 106 | TaskA taskA; 107 | 108 | TaskB taskBs[4]; 109 | for( auto& task : taskBs ) 110 | { 111 | task.SetDependency(task.m_Dependency,&taskA); 112 | } 113 | 114 | TaskC taskC; // Task C is a pinned task, defaults to running on thread 0 (this thread) 115 | taskC.SetDependenciesArr( taskC.m_Dependencies, taskBs ); 116 | 117 | TaskD taskDs[10]; 118 | for( auto& task : taskDs ) 119 | { 120 | task.SetDependency(task.m_Dependency,&taskC); 121 | } 122 | 123 | TasksFinished tasksFinished; 124 | tasksFinished.SetDependenciesVec( tasksFinished.m_Dependencies, taskDs ); 125 | 126 | TaskLauncher taskLauncher; 127 | taskLauncher.m_pTaskToLaunch = &taskA; //start with task A 128 | 129 | // we need to add a dependency on the launcher to tasksFinished otherwise 130 | // tasksFinished might be labelled as complete before taskA launched 131 | tasksFinished.SetDependency( tasksFinished.m_DepencyOnLauncher, &taskLauncher ); 132 | 133 | // run graph many times 134 | for( int run = 0; run< RUNS; ++run ) 135 | { 136 | printf("Run %d / %d.....\n", run+1, RUNS); 137 | 138 | g_TS.AddTaskSetToPipe( &taskLauncher ); 139 | 140 | g_TS.WaitforTask( &tasksFinished ); 141 | printf("Tasks Finished\n"); 142 | } 143 | 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /example/TestWaitforTask.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | #include 24 | 25 | enki::TaskScheduler g_TS; 26 | uint32_t g_Iteration; 27 | 28 | std::atomic g_WaitForTaskCompletion(0); 29 | std::atomic g_WaitCount(0); 30 | 31 | struct SlowTask : enki::ITaskSet 32 | { 33 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override 34 | { 35 | // fake slow task with timer 36 | Timer timer; 37 | timer.Start(); 38 | while( timer.GetTimeMS() < waitTime ) 39 | { 40 | } 41 | } 42 | 43 | double waitTime; 44 | }; 45 | 46 | struct SlowPinnedTask : enki::IPinnedTask 47 | { 48 | void Execute() override 49 | { 50 | // fake slow task with timer 51 | Timer timer; 52 | timer.Start(); 53 | while( timer.GetTimeMS() < waitTime ) 54 | { 55 | } 56 | } 57 | 58 | double waitTime; 59 | }; 60 | 61 | struct WaitingTask : enki::ITaskSet 62 | { 63 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override 64 | { 65 | int numWaitTasks = maxWaitasks - depth; 66 | for( int t = 0; t < numWaitTasks; ++t ) 67 | { 68 | pWaitingTasks[t] = new WaitingTask; 69 | pWaitingTasks[t]->depth = depth + 1; 70 | g_TS.AddTaskSetToPipe( pWaitingTasks[t] ); 71 | } 72 | for( SlowTask& task : tasks ) 73 | { 74 | task.m_SetSize = 1000; 75 | task.waitTime = 0.00001 * double( rand() % 100 ); 76 | g_TS.AddTaskSetToPipe( &task ); 77 | } 78 | 79 | for( SlowTask& task : tasks ) 80 | { 81 | ++g_WaitCount; 82 | g_TS.WaitforTask( &task ); 83 | 84 | // we add a random wait for pinned task here. 85 | uint32_t randThread = (uint32_t)rand() % g_TS.GetNumTaskThreads(); 86 | pinnedTask.threadNum = randThread; 87 | pinnedTask.waitTime = 0.00001 * double( rand() % 10 ); 88 | g_TS.AddPinnedTask( &pinnedTask ); 89 | g_TS.WaitforTask( &pinnedTask ); 90 | } 91 | 92 | for( int t = 0; t < numWaitTasks; ++t ) 93 | { 94 | ++g_WaitCount; 95 | g_TS.WaitforTask( pWaitingTasks[t] ); 96 | } 97 | } 98 | 99 | virtual ~WaitingTask() 100 | { 101 | for( WaitingTask* pWaitingTask : pWaitingTasks ) 102 | { 103 | delete pWaitingTask; 104 | } 105 | } 106 | SlowTask tasks[4]; 107 | SlowPinnedTask pinnedTask; 108 | int32_t depth = 0; 109 | static constexpr int maxWaitasks = 4; 110 | WaitingTask* pWaitingTasks[maxWaitasks] = {}; 111 | }; 112 | 113 | 114 | 115 | // This example demonstrates how to run a long running task alongside tasks 116 | // which must complete as early as possible using priorities. 117 | int main(int argc, const char * argv[]) 118 | { 119 | enki::TaskSchedulerConfig config; 120 | config.profilerCallbacks.waitForTaskCompleteStart = []( uint32_t threadnum_ ) { ++g_WaitCount; }; 121 | config.profilerCallbacks.waitForTaskCompleteSuspendStart = []( uint32_t threadnum_ ) { ++g_WaitForTaskCompletion; }; 122 | g_TS.Initialize( config ); 123 | for( g_Iteration = 0; g_Iteration < 1000; ++g_Iteration ) 124 | { 125 | printf( "\tIteration %d: Waits: %d blocking waits: %d\n", 126 | g_Iteration, g_WaitCount.load(), g_WaitForTaskCompletion.load() ); 127 | WaitingTask taskRoot; 128 | g_TS.AddTaskSetToPipe( &taskRoot ); 129 | g_TS.WaitforAll(); 130 | } 131 | return 0; 132 | } 133 | -------------------------------------------------------------------------------- /example/ParallelSum_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | 24 | enkiTaskScheduler* pETS; 25 | 26 | 27 | typedef struct ParallelSumTaskSetArgs 28 | { 29 | uint64_t* pPartialSums; 30 | uint32_t numPartialSums; 31 | } ParallelSumTaskSetArgs; 32 | 33 | void ParallelSumTaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ) 34 | { 35 | ParallelSumTaskSetArgs args; 36 | uint64_t sum, i; 37 | 38 | args = *(ParallelSumTaskSetArgs*)pArgs_; 39 | 40 | sum = args.pPartialSums[threadnum_]; 41 | for( i = start_; i < end_; ++i ) 42 | { 43 | sum += i + 1; 44 | } 45 | args.pPartialSums[threadnum_] = sum; 46 | } 47 | 48 | typedef struct ParallelReductionSumTaskSetArgs 49 | { 50 | ParallelSumTaskSetArgs sumArgs; 51 | uint32_t numParrallelSums; 52 | enkiTaskSet* pPSumTask; 53 | uint64_t sum; 54 | } ParallelReductionSumTaskSetArgs; 55 | 56 | void ParallelReductionSumTaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ) 57 | { 58 | ParallelReductionSumTaskSetArgs* pArgs; 59 | uint64_t sum, i; 60 | 61 | pArgs = (ParallelReductionSumTaskSetArgs*)pArgs_; 62 | 63 | sum = 0; 64 | for( i = 0; i < pArgs->sumArgs.numPartialSums; ++i ) 65 | { 66 | sum += pArgs->sumArgs.pPartialSums[i]; 67 | } 68 | 69 | pArgs->sum = sum; 70 | } 71 | 72 | static const uint32_t REPEATS = 10; 73 | 74 | int main(int argc, const char * argv[]) 75 | { 76 | uint32_t run; 77 | uint64_t i, serialSum; 78 | ParallelReductionSumTaskSetArgs parRedSumTaskSetArgs; 79 | enkiTaskSet* pPSumTask; 80 | enkiTaskSet* pPSumReductionTask; 81 | enkiDependency* pDependencypPSumReductionOnpPSum; 82 | 83 | pETS = enkiNewTaskScheduler(); 84 | enkiInitTaskScheduler( pETS ); 85 | 86 | pPSumTask = enkiCreateTaskSet( pETS, ParallelSumTaskSetFunc ); 87 | pPSumReductionTask = enkiCreateTaskSet( pETS, ParallelReductionSumTaskSetFunc ); 88 | pDependencypPSumReductionOnpPSum = enkiCreateDependency( pETS ); 89 | enkiSetDependency( pDependencypPSumReductionOnpPSum, 90 | enkiGetCompletableFromTaskSet( pPSumTask ), 91 | enkiGetCompletableFromTaskSet( pPSumReductionTask ) ); 92 | 93 | parRedSumTaskSetArgs.pPSumTask = pPSumTask; 94 | parRedSumTaskSetArgs.numParrallelSums = 10 * 1024 * 1024; 95 | parRedSumTaskSetArgs.sumArgs.numPartialSums = enkiGetNumTaskThreads( pETS ); 96 | parRedSumTaskSetArgs.sumArgs.pPartialSums = (uint64_t*)malloc( 97 | sizeof(uint64_t) * parRedSumTaskSetArgs.sumArgs.numPartialSums ); 98 | enkiSetArgsTaskSet( pPSumReductionTask, &parRedSumTaskSetArgs ); 99 | 100 | struct enkiParamsTaskSet parSumTaskParams = enkiGetParamsTaskSet( pPSumTask ); 101 | parSumTaskParams.pArgs = &parRedSumTaskSetArgs.sumArgs; 102 | parSumTaskParams.setSize = parRedSumTaskSetArgs.numParrallelSums; 103 | enkiSetParamsTaskSet( pPSumTask, parSumTaskParams ); 104 | 105 | for( run = 0; run< REPEATS; ++run ) 106 | { 107 | // reset partial sums 108 | memset( parRedSumTaskSetArgs.sumArgs.pPartialSums, 0, 109 | sizeof(uint64_t) * parRedSumTaskSetArgs.sumArgs.numPartialSums ); 110 | 111 | // add first task, parallel sum 112 | enkiAddTaskSet( pETS, pPSumTask ); 113 | 114 | // wait for reduction which will run due to dependencies 115 | enkiWaitForTaskSet( pETS, pPSumReductionTask ); 116 | 117 | printf("Parallel Example complete sum: \t %llu\n", (long long unsigned int)parRedSumTaskSetArgs.sum ); 118 | 119 | serialSum = 0; 120 | for( i = 0; i < parRedSumTaskSetArgs.numParrallelSums; ++i ) 121 | { 122 | serialSum += i + 1; 123 | } 124 | 125 | printf("Serial Example complete sum: \t %llu\n", (long long unsigned int)serialSum ); 126 | 127 | if( serialSum != parRedSumTaskSetArgs.sum ) 128 | { 129 | printf("ERROR: Serial sum does not match parallel sum\n"); 130 | } 131 | } 132 | 133 | free( parRedSumTaskSetArgs.sumArgs.pPartialSums ); 134 | 135 | enkiDeleteDependency( pETS, pDependencypPSumReductionOnpPSum ); 136 | enkiDeleteTaskSet( pETS, pPSumReductionTask ); 137 | enkiDeleteTaskSet( pETS, pPSumTask ); 138 | 139 | enkiDeleteTaskScheduler( pETS ); 140 | } 141 | -------------------------------------------------------------------------------- /example/TaskThroughput.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace enki; 29 | 30 | 31 | const uint32_t numTasks = 1024*1024; 32 | 33 | TaskScheduler g_TS; 34 | 35 | 36 | struct ConsumeTask : ITaskSet 37 | { 38 | static ConsumeTask tasks[numTasks]; 39 | 40 | struct Count 41 | { 42 | // prevent false sharing. 43 | uint32_t count; 44 | char cacheline[64]; 45 | }; 46 | static Count* pCount; 47 | static uint32_t numCount; 48 | 49 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 50 | { 51 | ++pCount[threadnum_].count; 52 | } 53 | 54 | static void Init() 55 | { 56 | delete[] ConsumeTask::pCount; 57 | numCount = g_TS.GetNumTaskThreads(); 58 | ConsumeTask::pCount = new Count[ numCount ]; 59 | memset( pCount, 0, sizeof(Count) * numCount ); 60 | } 61 | }; 62 | 63 | ConsumeTask ConsumeTask::tasks[numTasks]; 64 | ConsumeTask::Count* ConsumeTask::pCount = NULL; 65 | uint32_t ConsumeTask::numCount = 0; 66 | 67 | 68 | 69 | struct CreateTasks : ITaskSet 70 | { 71 | CreateTasks() 72 | { 73 | m_SetSize = numTasks; 74 | } 75 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 76 | { 77 | for( uint32_t i=range_.start; i = WARMUPS ) 141 | { 142 | avTime2 += tParallel.GetTimeMS() * tParallel.GetTimeMS(); 143 | avTime += tParallel.GetTimeMS() / RUNS; 144 | } 145 | } 146 | 147 | printf("\nAverage Time for %d Hardware Threads: %fms, rate: %f M tasks/s. %d errors found.\n", numThreads, avTime, numTasks / avTime / 1000.0f, totalErrors ); 148 | 149 | times[numThreads-1] = avTime; 150 | stdev[numThreads-1] = sqrt(RUNS * avTime2 - (RUNS * avTime)*(RUNS * avTime)) / RUNS; 151 | } 152 | 153 | printf("\nHardware Threads, Time, std, MTasks/s, Perf Multiplier\n" ); 154 | for( uint32_t numThreads = 1; numThreads <= maxThreads; ++numThreads ) 155 | { 156 | printf("%d, %f, %f, %f, %f\n", numThreads, times[numThreads-1], stdev[numThreads-1], numTasks / times[numThreads-1] / 1000.0f, times[0] / times[numThreads-1] ); 157 | } 158 | 159 | 160 | delete[] times; 161 | 162 | return 0; 163 | } 164 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | push: 5 | branches: [ master, dev ] 6 | workflow_dispatch: 7 | permissions: 8 | statuses: write 9 | contents: read 10 | 11 | jobs: 12 | build-linux-clang: 13 | name: Linux, Clang 14 | runs-on: ubuntu-latest 15 | env: 16 | CC: clang 17 | CXX: clang++ 18 | CFLAGS: -Werror 19 | CXXFLAGS: -Werror 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | 24 | - name: Configure static library 25 | run: cmake -S . -B build-static -D ENKITS_SANITIZE=ON 26 | - name: Build static library 27 | run: cmake --build build-static --parallel 28 | - name: Test static library 29 | run: ./build-static/TestAll 30 | 31 | - name: Configure shared library 32 | run: cmake -S . -B build-shared -D ENKITS_BUILD_SHARED=ON 33 | - name: Build shared library 34 | run: cmake --build build-shared --parallel 35 | - name: Test shared library 36 | run: ./build-shared/TestAll 37 | 38 | build-linux-gcc: 39 | name: Linux, gcc 40 | runs-on: ubuntu-latest 41 | env: 42 | CC: gcc 43 | CXX: g++ 44 | CFLAGS: -Werror 45 | CXXFLAGS: -Werror 46 | 47 | steps: 48 | - uses: actions/checkout@v3 49 | 50 | - name: Configure static library 51 | run: cmake -S . -B build-static -D ENKITS_SANITIZE=ON 52 | - name: Build static library 53 | run: cmake --build build-static --parallel 54 | - name: Test static library 55 | run: ./build-static/TestAll 56 | 57 | - name: Configure shared library 58 | run: cmake -S . -B build-shared -D ENKITS_BUILD_SHARED=ON 59 | - name: Build shared library 60 | run: cmake --build build-shared --parallel 61 | - name: Test shared library 62 | run: ./build-shared/TestAll 63 | 64 | build-ubuntu-zig: 65 | name: Linux, zig 66 | runs-on: ubuntu-latest 67 | steps: 68 | - uses: actions/checkout@v3 69 | - uses: mlugg/setup-zig@v2 70 | with: 71 | version: 0.15.1 72 | 73 | - name: Build static library and priorities 74 | run: zig build -Dbuild_examples=true -Dshared=false -Dtask_priorities=4 75 | - name: Test static library 76 | run: ./zig-out/bin/TestAll 77 | 78 | - name: Build shared library 79 | run: zig build -Dbuild_examples=true -Dshared=true 80 | - name: Test shared library 81 | run: ./zig-out/bin/TestAll 82 | 83 | 84 | build-macos-cocoa-clang: 85 | name: Cocoa (macOS, Clang) 86 | runs-on: macos-latest 87 | env: 88 | CFLAGS: -Werror 89 | CXXFLAGS: -Werror 90 | steps: 91 | - uses: actions/checkout@v3 92 | 93 | - name: Configure static library 94 | run: cmake -S . -B build-static -D ENKITS_SANITIZE=ON 95 | - name: Build static library 96 | run: cmake --build build-static --parallel 97 | - name: Test static library 98 | run: ./build-static/TestAll 99 | 100 | - name: Configure shared library 101 | run: cmake -S . -B build-shared -D ENKITS_BUILD_SHARED=ON 102 | - name: Build shared library 103 | run: cmake --build build-shared --parallel 104 | - name: Test shared library 105 | run: ./build-shared/TestAll 106 | 107 | build-windows-win32-vs2022: 108 | name: Win32 (Windows, VS2022) 109 | runs-on: windows-latest 110 | env: 111 | CFLAGS: /WX 112 | CXXFLAGS: /WX 113 | steps: 114 | - uses: actions/checkout@v3 115 | 116 | - name: Setup MSVC dev command prompt 117 | uses: TheMrMilchmann/setup-msvc-dev@v3 118 | with: 119 | arch: x64 120 | 121 | - name: Configure static library 122 | run: cmake -S . -B build-static -G "Visual Studio 17 2022" -D ENKITS_SANITIZE=ON 123 | - name: Build static library 124 | run: cmake --build build-static --parallel 125 | - name: Test static library 126 | run: .\build-static\Debug\TestAll.exe 127 | 128 | - name: Configure shared library 129 | run: cmake -S . -B build-shared -G "Visual Studio 17 2022" -D ENKITS_BUILD_SHARED=ON 130 | - name: Build shared library 131 | run: cmake --build build-shared --parallel 132 | - name: Test shared library 133 | run: .\build-shared\Debug\TestAll.exe 134 | 135 | build-windows-win32-zig: 136 | name: Win32 (Windows, zig) 137 | runs-on: windows-latest 138 | steps: 139 | - uses: actions/checkout@v3 140 | - uses: mlugg/setup-zig@v2 141 | with: 142 | version: 0.15.1 143 | 144 | - name: Build static library and priorities 145 | run: zig build -Dbuild_examples=true -Dshared=false -Dtask_priorities=4 146 | - name: Test static library 147 | run: .\zig-out\bin\TestAll.exe 148 | 149 | - name: Build shared library 150 | run: zig build -Dbuild_examples=true -Dshared=true 151 | - name: Test shared library 152 | run: .\zig-out\bin\TestAll.exe 153 | -------------------------------------------------------------------------------- /example/Dependencies_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | 24 | enkiTaskScheduler* pETS; 25 | 26 | 27 | void TaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ) 28 | { 29 | (void)start_; (void)end_; 30 | char* str = (char*)pArgs_; 31 | printf("%s on thread %u\n", str, threadnum_); 32 | } 33 | 34 | void PinnedTaskFunc( void* pArgs_ ) 35 | { 36 | char* str = (char*)pArgs_; 37 | printf("%s Pinned task on thread 0, should be %u\n", str, enkiGetThreadNum( pETS ) ); 38 | } 39 | 40 | 41 | #define NUM_TASK_B 4 42 | #define NUM_TASK_D 2 43 | 44 | int main(int argc, const char * argv[]) 45 | { 46 | int run; 47 | enkiTaskSet* pTaskA; 48 | enkiTaskSet* pTaskB[NUM_TASK_B]; 49 | enkiDependency* pTaskBDependencyToA[NUM_TASK_B]; 50 | enkiPinnedTask* pPinnedTaskC; 51 | enkiDependency* pPinnedTaskCDependencyToBs[NUM_TASK_B]; 52 | enkiTaskSet* pTaskD[NUM_TASK_D]; 53 | enkiDependency* pTaskDDependencyToC[NUM_TASK_D]; 54 | enkiCompletable* pCompletableFinished; // A completable can be used on it's own to check if tasks complete. 55 | enkiDependency* pDependencyToD[NUM_TASK_D]; 56 | 57 | pETS = enkiNewTaskScheduler(); 58 | enkiInitTaskScheduler( pETS ); 59 | 60 | // create tasks and set dependencies once, reuse many times 61 | pTaskA = enkiCreateTaskSet( pETS, TaskSetFunc ); 62 | enkiSetArgsTaskSet( pTaskA, "A" ); 63 | for( int i=0; i 22 | 23 | using namespace enki; 24 | 25 | TaskScheduler g_TS; 26 | static std::atomic g_Run; 27 | 28 | enum class IOThreadId 29 | { 30 | FILE_IO, // more than one file io thread may be useful if handling lots of small files 31 | NETWORK_IO_0, 32 | NETWORK_IO_1, 33 | NETWORK_IO_2, 34 | NUM, 35 | }; 36 | 37 | struct ParallelTaskSet : ITaskSet 38 | { 39 | ParallelTaskSet() { m_SetSize = 100; } 40 | 41 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 42 | { 43 | bool bIsIOThread = threadnum_ >= g_TS.GetNumTaskThreads() - (uint32_t)IOThreadId::NUM; 44 | if( bIsIOThread ) 45 | { 46 | assert( false ); //for this example this is an error - but external threads can run tasksets in general 47 | printf(" Run %d: ParallelTaskSet on thread %d which is an IO thread\n", g_Run.load(), threadnum_); 48 | } 49 | else 50 | { 51 | printf(" Run %d: ParallelTaskSet on thread %d which is not an IO thread\n", g_Run.load(), threadnum_); 52 | } 53 | } 54 | }; 55 | 56 | struct RunPinnedTaskLoopTask : IPinnedTask 57 | { 58 | void Execute() override 59 | { 60 | while( !g_TS.GetIsShutdownRequested() ) 61 | { 62 | g_TS.WaitForNewPinnedTasks(); // this thread will 'sleep' until there are new pinned tasks 63 | g_TS.RunPinnedTasks(); 64 | } 65 | } 66 | }; 67 | 68 | struct PretendDoFileIO : IPinnedTask 69 | { 70 | void Execute() override 71 | { 72 | printf(" Run %d: PretendDoFileIO on thread %d\n", g_Run.load(), threadNum ); 73 | // sleep used as a 'pretend' blocking workload 74 | std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); 75 | } 76 | }; 77 | 78 | struct PretendDoNetworkIO : IPinnedTask 79 | { 80 | void Execute() override 81 | { 82 | printf(" Run %d: PretendDoNetworkIO on thread %d\n", g_Run.load(), threadNum ); 83 | // sleep used as a 'pretend' blocking workload 84 | std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) ); 85 | } 86 | }; 87 | 88 | static const int REPEATS = 5; 89 | 90 | int main(int argc, const char * argv[]) 91 | { 92 | enki::TaskSchedulerConfig config; 93 | 94 | // In this example we create more threads than the hardware can run, 95 | // because the IO threads will spend most of their time idle or blocked 96 | // and therefore not scheduled for CPU time by the OS 97 | config.numTaskThreadsToCreate += (uint32_t)IOThreadId::NUM; 98 | 99 | g_TS.Initialize( config ); 100 | 101 | // in this example we place our IO threads at the end 102 | uint32_t theadNumIOStart = g_TS.GetNumTaskThreads() - (uint32_t)IOThreadId::NUM; 103 | RunPinnedTaskLoopTask runPinnedTaskLoopTasks[ (uint32_t)IOThreadId::NUM ]; 104 | 105 | for( uint32_t ioThreadID = 0; ioThreadID < (uint32_t)IOThreadId::NUM; ++ioThreadID ) 106 | { 107 | runPinnedTaskLoopTasks[ioThreadID].threadNum = ioThreadID + theadNumIOStart; 108 | g_TS.AddPinnedTask( &runPinnedTaskLoopTasks[ioThreadID] ); 109 | } 110 | 111 | 112 | for( g_Run = 0; g_Run< REPEATS; ++g_Run ) 113 | { 114 | printf("Run %d\n", g_Run.load() ); 115 | 116 | // set of a ParallelTaskSet 117 | // to demonstrate can perform work on enkiTS worker threads but WaitForNewPinnedTasks will 118 | // not perform work. This can be used to fully subscribe machine with enkiTS worker threads but 119 | // have extra IO bound threads to handle blocking tasks 120 | ParallelTaskSet parallelTaskSet; 121 | g_TS.AddTaskSetToPipe( ¶llelTaskSet ); 122 | 123 | // Send pretend file IO task to external thread FILE_IO 124 | PretendDoFileIO pretendDoFileIO; 125 | pretendDoFileIO.threadNum = (uint32_t)IOThreadId::FILE_IO + theadNumIOStart; 126 | g_TS.AddPinnedTask( &pretendDoFileIO ); 127 | 128 | // Send pretend network IO tasks to external thread NETWORK_IO_0 ... NUMEXTERNALTHREADS 129 | PretendDoNetworkIO pretendDoNetworkIO[ (uint32_t)IOThreadId::NUM - (uint32_t)IOThreadId::NETWORK_IO_0 ]; 130 | for( uint32_t ioThreadID = (uint32_t)IOThreadId::NETWORK_IO_0; ioThreadID < (uint32_t)IOThreadId::NUM; ++ioThreadID ) 131 | { 132 | pretendDoNetworkIO[ioThreadID-(uint32_t)IOThreadId::NETWORK_IO_0].threadNum = ioThreadID + theadNumIOStart; 133 | g_TS.AddPinnedTask( &pretendDoNetworkIO[ ioThreadID - (uint32_t)IOThreadId::NETWORK_IO_0 ] ); 134 | } 135 | 136 | g_TS.WaitforTask( ¶llelTaskSet ); 137 | 138 | // in this example we need to wait for IO tasks to complete before running next loop 139 | g_TS.WaitforTask( &pretendDoFileIO ); 140 | for( uint32_t ioThreadID = (uint32_t)IOThreadId::NETWORK_IO_0; ioThreadID < (uint32_t)IOThreadId::NUM; ++ioThreadID ) 141 | { 142 | g_TS.WaitforTask( &pretendDoNetworkIO[ ioThreadID - (uint32_t)IOThreadId::NETWORK_IO_0 ] ); 143 | } 144 | } 145 | 146 | // ensure runPinnedTaskLoopTasks complete by explicitly calling WaitforAllAndShutdown 147 | g_TS.WaitforAllAndShutdown(); 148 | 149 | return 0; 150 | } 151 | -------------------------------------------------------------------------------- /example/ParallelSum.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | using namespace enki; 28 | 29 | 30 | 31 | 32 | 33 | TaskScheduler g_TS; 34 | 35 | struct ParallelSumTaskSet : ITaskSet 36 | { 37 | struct Count 38 | { 39 | // prevent false sharing. 40 | uint64_t count; 41 | char cacheline[64]; 42 | }; 43 | Count* m_pPartialSums; 44 | uint32_t m_NumPartialSums; 45 | 46 | ParallelSumTaskSet( uint32_t size_ ) : m_pPartialSums(NULL), m_NumPartialSums(0) { m_SetSize = size_; m_MinRange = 10 * 1024; } 47 | virtual ~ParallelSumTaskSet() 48 | { 49 | delete[] m_pPartialSums; 50 | } 51 | 52 | void Init( uint32_t numPartialSums_ ) 53 | { 54 | delete[] m_pPartialSums; 55 | m_NumPartialSums =numPartialSums_ ; 56 | m_pPartialSums = new Count[ m_NumPartialSums ]; 57 | memset( m_pPartialSums, 0, sizeof(Count)*m_NumPartialSums ); 58 | } 59 | 60 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 61 | { 62 | assert( m_pPartialSums && m_NumPartialSums ); 63 | uint64_t sum = m_pPartialSums[threadnum_].count; 64 | for( uint64_t i = range_.start; i < range_.end; ++i ) 65 | { 66 | sum += i + 1; 67 | } 68 | m_pPartialSums[threadnum_].count = sum; 69 | } 70 | 71 | }; 72 | 73 | struct ParallelReductionSumTaskSet : ITaskSet 74 | { 75 | ParallelSumTaskSet* m_pParallelSum; 76 | Dependency m_Dependency; 77 | uint64_t m_FinalSum; 78 | 79 | ParallelReductionSumTaskSet( ParallelSumTaskSet* pParallelSum_ ) : m_pParallelSum( pParallelSum_ ), m_Dependency( pParallelSum_, this ), m_FinalSum(0) 80 | { 81 | } 82 | 83 | void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override 84 | { 85 | for( uint32_t i = 0; i < m_pParallelSum->m_NumPartialSums; ++i ) 86 | { 87 | m_FinalSum += m_pParallelSum->m_pPartialSums[i].count; 88 | } 89 | } 90 | }; 91 | 92 | static const int WARMUPS = 10; 93 | static const int RUNS = 20; 94 | static const int REPEATS = RUNS + WARMUPS; 95 | 96 | int main(int argc, const char * argv[]) 97 | { 98 | uint32_t maxThreads = enki::GetNumHardwareThreads(); 99 | double* avTimes = new double[ maxThreads ]; 100 | 101 | 102 | // start by measuring serial 103 | double avSerial = 0.0f; 104 | uint32_t setSize = 100 * 1024 * 1024; 105 | uint64_t sumSerial; 106 | for( int run = 0; run< REPEATS; ++run ) 107 | { 108 | Timer tSerial; 109 | tSerial.Start(); 110 | 111 | ParallelSumTaskSet serialTask( setSize ); 112 | serialTask.Init( 1 ); 113 | TaskSetPartition range = { 0, setSize }; 114 | 115 | serialTask.ExecuteRange( range, 0 ); 116 | sumSerial = serialTask.m_pPartialSums[0].count; 117 | 118 | tSerial.Stop(); 119 | 120 | if( run >= WARMUPS ) 121 | { 122 | avSerial += tSerial.GetTimeMS() / RUNS; 123 | } 124 | printf("Serial Example complete in \t%fms,\t sum: %" PRIu64 "\n", tSerial.GetTimeMS(), sumSerial ); 125 | } 126 | 127 | // now measure parallel 128 | for( uint32_t numThreads = 1; numThreads <= maxThreads; ++numThreads ) 129 | { 130 | g_TS.Initialize(numThreads); 131 | double avTime = 0.0; 132 | 133 | for( int run = 0; run< REPEATS; ++run ) 134 | { 135 | 136 | printf("Run %d.....\n", run); 137 | Timer tParallel; 138 | tParallel.Start(); 139 | 140 | ParallelSumTaskSet parallelSumTask( setSize ); 141 | parallelSumTask.Init( g_TS.GetNumTaskThreads() ); 142 | ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask ); 143 | 144 | g_TS.AddTaskSetToPipe( ¶llelSumTask ); 145 | g_TS.WaitforTask( ¶llelReductionSumTaskSet ); 146 | 147 | tParallel.Stop(); 148 | 149 | 150 | printf("Parallel Example complete in \t%fms,\t sum: %" PRIu64 "\n", tParallel.GetTimeMS(), parallelReductionSumTaskSet.m_FinalSum ); 151 | 152 | if( run >= WARMUPS ) 153 | { 154 | avTime += tParallel.GetTimeMS() / RUNS; 155 | } 156 | 157 | if( sumSerial != parallelReductionSumTaskSet.m_FinalSum ) 158 | { 159 | printf( "ERROR: sums do not match\n" ); 160 | return -1; 161 | } 162 | 163 | printf("Speed Up Serial / Parallel: %f\n\n", avSerial / tParallel.GetTimeMS() ); 164 | 165 | } 166 | avTimes[numThreads-1] = avTime; 167 | printf("\nAverage Time for %d Hardware Threads Serial / Parallel: %f, Speed Up over serial: %f\n", numThreads, avTime, avSerial / avTime ); 168 | } 169 | 170 | printf("\nTime Serial (no task system): %f\n", avSerial ); 171 | printf("\nHardware Threads, Av Time, Av Speed Up over serial, Av Speed up over 1 thread\n" ); 172 | for( uint32_t numThreads = 1; numThreads <= maxThreads; ++numThreads ) 173 | { 174 | printf("%d, %f, %f, %f\n", numThreads, avTimes[numThreads-1], avSerial / avTimes[numThreads-1], avTimes[0] / avTimes[numThreads-1] ); 175 | } 176 | 177 | return 0; 178 | } 179 | -------------------------------------------------------------------------------- /example/TaskOverhead.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | #define __STDC_FORMAT_MACROS 24 | #include 25 | #undef __STDC_FORMAT_MACROS 26 | #include 27 | #include 28 | 29 | using namespace enki; 30 | 31 | 32 | 33 | 34 | 35 | TaskScheduler g_TS; 36 | 37 | struct ParallelSumTaskSet : ITaskSet 38 | { 39 | struct Count 40 | { 41 | // prevent false sharing. 42 | uint64_t count; 43 | char cacheline[64]; 44 | }; 45 | Count* m_pPartialSums; 46 | uint32_t m_NumPartialSums; 47 | 48 | ParallelSumTaskSet( uint32_t size_ ) : m_pPartialSums(NULL), m_NumPartialSums(0) { m_SetSize = size_; m_MinRange = 1024; } 49 | virtual ~ParallelSumTaskSet() 50 | { 51 | delete[] m_pPartialSums; 52 | } 53 | 54 | void Init( uint32_t numPartialSums_ ) 55 | { 56 | delete[] m_pPartialSums; 57 | m_NumPartialSums =numPartialSums_ ; 58 | m_pPartialSums = new Count[ m_NumPartialSums ]; 59 | memset( m_pPartialSums, 0, sizeof(Count)*m_NumPartialSums ); 60 | } 61 | 62 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 63 | { 64 | assert( m_pPartialSums && m_NumPartialSums ); 65 | uint64_t sum = m_pPartialSums[threadnum_].count; 66 | for( uint64_t i = range_.start; i < range_.end; ++i ) 67 | { 68 | sum += i + 1; 69 | } 70 | m_pPartialSums[threadnum_].count = sum; 71 | } 72 | 73 | }; 74 | 75 | struct ParallelReductionSumTaskSet : ITaskSet 76 | { 77 | ParallelSumTaskSet* m_pParallelSum; 78 | Dependency m_Dependency; 79 | uint64_t m_FinalSum; 80 | 81 | ParallelReductionSumTaskSet( ParallelSumTaskSet* pParallelSum_ ) : m_pParallelSum( pParallelSum_ ), m_Dependency( pParallelSum_, this ), m_FinalSum(0) 82 | { 83 | } 84 | 85 | void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override 86 | { 87 | for( uint32_t i = 0; i < m_pParallelSum->m_NumPartialSums; ++i ) 88 | { 89 | m_FinalSum += m_pParallelSum->m_pPartialSums[i].count; 90 | } 91 | } 92 | }; 93 | 94 | static const int WARMUPS = 10; 95 | static const int RUNS = 100; 96 | static const int REPEATS = RUNS + WARMUPS; 97 | 98 | static const int DATA_POINTS = 50; 99 | 100 | uint32_t GetSetSizeForDataPoint( uint32_t dataPoint_ ) 101 | { 102 | dataPoint_++; 103 | return dataPoint_ * dataPoint_ * 1024; 104 | } 105 | 106 | int main(int argc, const char * argv[]) 107 | { 108 | double* avTimeTakenMS = new double[ DATA_POINTS ]; 109 | double* avSerialTimeTakenMS = new double[ DATA_POINTS ]; 110 | 111 | 112 | g_TS.Initialize(); 113 | for( uint32_t dataPoint = 0; dataPoint < DATA_POINTS; ++dataPoint ) 114 | { 115 | double avTimeMS = 0.0; 116 | uint32_t setSize = GetSetSizeForDataPoint( dataPoint ); 117 | 118 | double avSerialTimeMS = 0.0f; 119 | uint64_t sumSerial; 120 | for( int run = 0; run< REPEATS; ++run ) 121 | { 122 | printf("Run %d.....\n", run); 123 | 124 | // start by measuring serial 125 | ParallelSumTaskSet serialTask( setSize ); 126 | serialTask.Init( 1 ); 127 | TaskSetPartition range = { 0, setSize }; 128 | 129 | Timer tSerial; 130 | tSerial.Start(); 131 | 132 | serialTask.ExecuteRange( range, 0 ); 133 | sumSerial = serialTask.m_pPartialSums[0].count; 134 | 135 | tSerial.Stop(); 136 | printf("Serial Example complete in \t%fms,\t sum: %" PRIu64 "\n", tSerial.GetTimeMS(), sumSerial ); 137 | 138 | 139 | // Now measure parallel 140 | ParallelSumTaskSet parallelSumTask( setSize ); 141 | parallelSumTask.Init( g_TS.GetNumTaskThreads() ); 142 | ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask ); 143 | 144 | Timer tParallel; 145 | tParallel.Start(); 146 | 147 | g_TS.AddTaskSetToPipe( ¶llelSumTask ); 148 | 149 | g_TS.WaitforTask( ¶llelReductionSumTaskSet ); 150 | 151 | tParallel.Stop(); 152 | printf("Parallel Example complete in \t%fms,\t sum: %" PRIu64 "\n", tParallel.GetTimeMS(), parallelReductionSumTaskSet.m_FinalSum ); 153 | 154 | if( sumSerial != parallelReductionSumTaskSet.m_FinalSum ) 155 | { 156 | printf("ERROR, sums do not match\n"); 157 | return -1; 158 | } 159 | 160 | if( run >= WARMUPS ) 161 | { 162 | avSerialTimeMS += tSerial.GetTimeMS() / RUNS; 163 | avTimeMS += tParallel.GetTimeMS() / RUNS; 164 | } 165 | } 166 | avSerialTimeTakenMS[ dataPoint ] = avSerialTimeMS; 167 | avTimeTakenMS[ dataPoint ] = avTimeMS; 168 | printf("\nAverage time for set size %d: %fms parallel, %fms serial\n", setSize, (float)avTimeMS, (float)avSerialTimeMS ); 169 | } 170 | 171 | printf("\nSet Size,\tTime Parallel/ms,\tTime Serial/ms\n" ); 172 | for( uint32_t dataPoint = 0; dataPoint < DATA_POINTS; ++dataPoint ) 173 | { 174 | printf("%8d,\t%f,\t%f\n", GetSetSizeForDataPoint( dataPoint ), (float)avTimeTakenMS[ dataPoint ], (float)avSerialTimeTakenMS[ dataPoint ] ); 175 | } 176 | 177 | delete[] avSerialTimeTakenMS; 178 | delete[] avTimeTakenMS; 179 | 180 | return 0; 181 | } 182 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project( enkiTS 4 | VERSION "1.11" 5 | DESCRIPTION "A C and C++ task scheduler for creating parallel programs" 6 | HOMEPAGE_URL "https://github.com/dougbinks/enkiTS" 7 | LANGUAGES C CXX) 8 | 9 | include(GNUInstallDirs) 10 | 11 | option( ENKITS_BUILD_C_INTERFACE "Build C interface" ON ) 12 | option( ENKITS_BUILD_EXAMPLES "Build example applications" ON ) 13 | option( ENKITS_BUILD_SHARED "Build shared library" OFF ) 14 | option( ENKITS_INSTALL "Generate installation target" OFF ) 15 | option( ENKITS_SANITIZE "Build with sanitizers" OFF) 16 | 17 | set( ENKITS_TASK_PRIORITIES_NUM "3" CACHE STRING "Number of task priorities, 1-5, 0 for defined by defaults in source" ) 18 | 19 | set( ENKITS_HEADERS 20 | src/LockLessMultiReadPipe.h 21 | src/TaskScheduler.h 22 | ) 23 | 24 | set( ENKITS_SRC 25 | src/TaskScheduler.cpp 26 | ) 27 | 28 | if( ENKITS_BUILD_C_INTERFACE ) 29 | list( APPEND ENKITS_HEADERS 30 | src/TaskScheduler_c.h 31 | ) 32 | 33 | list( APPEND ENKITS_SRC 34 | src/TaskScheduler_c.cpp 35 | ) 36 | endif() 37 | 38 | list( APPEND ENKITS_SRC ${ENKITS_HEADERS} ) 39 | 40 | if(ENKITS_SANITIZE) 41 | if(MSVC) 42 | add_compile_options(/fsanitize=address) 43 | add_link_options(/INCREMENTAL:NO) 44 | else() 45 | # add_compile_options(-fsanitize=thread -fno-omit-frame-pointer) 46 | add_compile_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined) 47 | add_link_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined) 48 | endif() 49 | endif() 50 | 51 | if( ENKITS_BUILD_SHARED ) 52 | add_library( enkiTS SHARED ${ENKITS_SRC} ) 53 | target_compile_definitions( enkiTS PRIVATE ENKITS_BUILD_DLL=1 ) 54 | target_compile_definitions( enkiTS INTERFACE ENKITS_DLL=1 ) 55 | set_target_properties( enkiTS PROPERTIES 56 | VERSION ${PROJECT_VERSION} 57 | SOVERSION ${PROJECT_VERSION_MAJOR}) 58 | if( UNIX ) 59 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 60 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") 61 | endif() 62 | endif () 63 | else() 64 | add_library( enkiTS STATIC ${ENKITS_SRC} ) 65 | endif() 66 | 67 | target_include_directories( enkiTS PUBLIC 68 | PUBLIC $ 69 | $ ) 70 | 71 | if( ENKITS_TASK_PRIORITIES_NUM GREATER "0" ) 72 | target_compile_definitions( enkiTS PUBLIC "ENKITS_TASK_PRIORITIES_NUM=${ENKITS_TASK_PRIORITIES_NUM}" ) 73 | endif() 74 | 75 | if( UNIX ) 76 | set( CMAKE_THREAD_PREFER_PTHREAD TRUE ) 77 | find_package( Threads REQUIRED ) 78 | if( CMAKE_USE_PTHREADS_INIT ) 79 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread" ) 80 | endif() 81 | target_link_libraries( enkiTS ${CMAKE_THREAD_LIBS_INIT} ) 82 | endif() 83 | 84 | if( ENKITS_INSTALL ) 85 | install( 86 | TARGETS enkiTS 87 | EXPORT enkiTSConfig 88 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 89 | install(FILES ${ENKITS_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/enkiTS) 90 | install( 91 | EXPORT enkiTSConfig 92 | NAMESPACE enkiTS:: 93 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/enkiTS) 94 | endif() 95 | 96 | if( UNIX ) 97 | SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" ) 98 | endif() 99 | if( APPLE ) 100 | SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++" ) 101 | endif() 102 | 103 | if( ENKITS_BUILD_EXAMPLES ) 104 | add_executable( ParallelSum example/ParallelSum.cpp example/Timer.h ) 105 | target_link_libraries(ParallelSum enkiTS ) 106 | 107 | add_executable( PinnedTask example/PinnedTask.cpp ) 108 | target_link_libraries(PinnedTask enkiTS ) 109 | 110 | add_executable( LambdaTask example/LambdaTask.cpp example/Timer.h ) 111 | target_link_libraries(LambdaTask enkiTS ) 112 | 113 | if( ENKITS_TASK_PRIORITIES_NUM GREATER "0" ) 114 | add_executable( Priorities example/Priorities.cpp ) 115 | target_link_libraries(Priorities enkiTS ) 116 | endif() 117 | 118 | add_executable( TaskThroughput example/TaskThroughput.cpp example/Timer.h ) 119 | target_link_libraries(TaskThroughput enkiTS ) 120 | 121 | add_executable( TaskOverhead example/TaskOverhead.cpp example/Timer.h ) 122 | target_link_libraries(TaskOverhead enkiTS ) 123 | 124 | add_executable( TestWaitforTask example/TestWaitforTask.cpp ) 125 | target_link_libraries(TestWaitforTask enkiTS ) 126 | 127 | add_executable( ExternalTaskThread example/ExternalTaskThread.cpp ) 128 | target_link_libraries(ExternalTaskThread enkiTS ) 129 | 130 | add_executable( CustomAllocator example/CustomAllocator.cpp ) 131 | target_link_libraries(CustomAllocator enkiTS ) 132 | 133 | add_executable( TestAll example/TestAll.cpp ) 134 | target_link_libraries(TestAll enkiTS ) 135 | 136 | add_executable( Dependencies example/Dependencies.cpp ) 137 | target_link_libraries(Dependencies enkiTS ) 138 | 139 | add_executable( CompletionAction example/CompletionAction.cpp ) 140 | target_link_libraries(CompletionAction enkiTS ) 141 | 142 | add_executable( WaitForNewPinnedTasks example/WaitForNewPinnedTasks.cpp ) 143 | target_link_libraries(WaitForNewPinnedTasks enkiTS ) 144 | 145 | if( ENKITS_BUILD_C_INTERFACE ) 146 | add_executable( ParallelSum_c example/ParallelSum_c.c ) 147 | target_link_libraries(ParallelSum_c enkiTS ) 148 | 149 | add_executable( PinnedTask_c example/PinnedTask_c.c ) 150 | target_link_libraries(PinnedTask_c enkiTS ) 151 | 152 | if( ENKITS_TASK_PRIORITIES_NUM GREATER "0" ) 153 | add_executable( Priorities_c example/Priorities_c.c ) 154 | target_link_libraries(Priorities_c enkiTS ) 155 | endif() 156 | 157 | add_executable( ExternalTaskThread_c example/ExternalTaskThread_c.c ) 158 | target_link_libraries(ExternalTaskThread_c enkiTS ) 159 | 160 | add_executable( CustomAllocator_c example/CustomAllocator_c.c ) 161 | target_link_libraries(CustomAllocator_c enkiTS ) 162 | 163 | add_executable( Dependencies_c example/Dependencies_c.c ) 164 | target_link_libraries(Dependencies_c enkiTS ) 165 | 166 | add_executable( CompletionAction_c example/CompletionAction_c.c ) 167 | target_link_libraries(CompletionAction_c enkiTS ) 168 | 169 | add_executable( WaitForNewPinnedTasks_c example/WaitForNewPinnedTasks_c.c ) 170 | target_link_libraries(WaitForNewPinnedTasks_c enkiTS ) 171 | 172 | endif() 173 | 174 | endif() 175 | -------------------------------------------------------------------------------- /example/CompletionAction.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #ifndef _WIN32 26 | #include 27 | #endif 28 | 29 | using namespace enki; 30 | 31 | TaskScheduler g_TS; 32 | 33 | static std::atomic gs_CountAsRun = {0}; 34 | static std::atomic gs_CountAsDeleted = {0}; 35 | static std::atomic gs_CountBsRun = {0}; 36 | static std::atomic gs_CountBsDeleted = {0}; 37 | 38 | struct CompletionActionDelete : ICompletable 39 | { 40 | Dependency m_Dependency; 41 | 42 | // We override OnDependenciesComplete to provide an 'action' which occurs after 43 | // the dependency task is complete. 44 | void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) 45 | { 46 | // Call base class OnDependenciesComplete BEFORE deleting depedent task or self 47 | ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ ); 48 | 49 | printf("CompletionActionDelete::OnDependenciesComplete called on thread %u\n", threadNum_ ); 50 | 51 | // In this example we delete the dependency, which is safe to do as the task 52 | // manager will not dereference it at this point. 53 | // However the dependency task should have no other dependents, 54 | // This class can have dependencies. 55 | delete m_Dependency.GetDependencyTask(); // also deletes this as member 56 | } 57 | }; 58 | 59 | struct SelfDeletingTaskB : ITaskSet 60 | { 61 | SelfDeletingTaskB() 62 | { 63 | m_TaskDeleter.SetDependency( m_TaskDeleter.m_Dependency, this ); 64 | } 65 | 66 | ~SelfDeletingTaskB() 67 | { 68 | ++gs_CountBsDeleted; 69 | printf("~SelfDeletingTaskB() called on thread %u\n\n", g_TS.GetThreadNum() ); 70 | } 71 | 72 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 73 | { 74 | if( 0 == range_.start ) 75 | { 76 | // whilst would normally loop over range_ doing work here we want to only output info once per task 77 | ++gs_CountBsRun; 78 | printf("SelfDeletingTaskB on thread %u with set size %u\n", threadnum_, m_SetSize); 79 | } 80 | } 81 | 82 | CompletionActionDelete m_TaskDeleter; 83 | Dependency m_Dependency; 84 | }; 85 | 86 | struct CompletionActionModifyDependentTaskAndDelete : ICompletable 87 | { 88 | Dependency m_Dependency; 89 | 90 | ITaskSet* m_pTaskToModify = nullptr; 91 | 92 | // We override OnDependenciesComplete to provide an 'action' which occurs after 93 | // the dependency task is complete. 94 | void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) 95 | { 96 | // Modify following task before calling OnDependenciesComplete 97 | m_pTaskToModify->m_SetSize = 10; 98 | 99 | // Call base class OnDependenciesComplete AFTER modifying any depedent task 100 | ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ ); 101 | 102 | printf("CompletionActionModifyDependentTaskAndDelete::OnDependenciesComplete called on thread %u\n", threadNum_ ); 103 | 104 | // In this example we delete the dependency, which is safe to do as the task 105 | // manager will not dereference it at this point. 106 | // However the dependency task should have no other dependents, 107 | // This class can have dependencies. 108 | delete m_Dependency.GetDependencyTask(); // also deletes this as member 109 | } 110 | }; 111 | 112 | struct SelfDeletingTaskA : ITaskSet 113 | { 114 | SelfDeletingTaskA() 115 | { 116 | m_TaskModifyAndDelete.SetDependency( m_TaskModifyAndDelete.m_Dependency, this ); 117 | SelfDeletingTaskB* pNextTask = new SelfDeletingTaskB(); 118 | 119 | // we set the dependency of pNextTask on the task deleter, not on this 120 | pNextTask->SetDependency( pNextTask->m_Dependency, &m_TaskModifyAndDelete ); 121 | 122 | // Set the completion actions task to modify to be the following task 123 | m_TaskModifyAndDelete.m_pTaskToModify = pNextTask; 124 | } 125 | 126 | ~SelfDeletingTaskA() 127 | { 128 | ++gs_CountAsDeleted; 129 | printf("~SelfDeletingTaskA() called on thread %u\n\n", g_TS.GetThreadNum() ); 130 | } 131 | 132 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 133 | { 134 | (void)range_; 135 | ++gs_CountAsRun; 136 | printf("SelfDeletingTaskA on thread %u with set size %u\n", threadnum_, m_SetSize); 137 | } 138 | 139 | CompletionActionModifyDependentTaskAndDelete m_TaskModifyAndDelete; 140 | }; 141 | 142 | static const int RUNS = 10; 143 | 144 | int main(int argc, const char * argv[]) 145 | { 146 | // This examples shows CompletionActions used to modify a following tasks parameters and delete tasks 147 | // Task Graph for this example (with names shortened to fit on screen): 148 | // 149 | // pTaskSetA 150 | // ->pCompletionActionA-Modify-ICompletable::OnDependenciesComplete-Delete 151 | // ->pTaskSetB 152 | // ->pCompletionActionB-ICompletable::OnDependenciesComplete-Delete 153 | // 154 | // Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA 155 | // so cannot be modified. 156 | 157 | g_TS.Initialize(); 158 | 159 | for( int run = 0; run< RUNS; ++run ) 160 | { 161 | g_TS.AddTaskSetToPipe( new SelfDeletingTaskA() ); 162 | } 163 | g_TS.WaitforAllAndShutdown(); 164 | 165 | printf("%d As run, %d deleted\n%d Bs run, %d deleted.", gs_CountAsRun.load(), gs_CountAsDeleted.load(), gs_CountBsRun.load(), gs_CountBsDeleted.load() ); 166 | 167 | if( gs_CountAsRun != gs_CountAsDeleted || 168 | gs_CountBsRun != gs_CountBsDeleted || 169 | gs_CountAsRun != gs_CountBsRun ) 170 | { 171 | printf("ERROR\n"); 172 | return 1; 173 | } 174 | 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /example/CompletionAction_c.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include 21 | #include 22 | #include 23 | 24 | enkiTaskScheduler* pETS; 25 | 26 | struct CompletionArgs_ModifyTask 27 | { 28 | enkiTaskSet* pTaskB; 29 | uint32_t run; 30 | }; 31 | 32 | struct CompletionArgs_DeleteTask 33 | { 34 | enkiTaskSet* pTask; 35 | enkiDependency* pDependency; // in this example only 1 or 0 dependencies, but generally could be an array 36 | enkiCompletionAction* pCompletionAction; 37 | uint32_t run; // only required for example output, not needed for a general purpose delete task 38 | }; 39 | 40 | // In this example all our TaskSet functions share the same args struct, but we could use different one 41 | struct TaskSetArgs 42 | { 43 | enkiTaskSet* pTask; 44 | const char* name; 45 | uint32_t run; 46 | }; 47 | 48 | void CompletionFunctionPreComplete_ModifyDependentTask( void* pArgs_, uint32_t threadNum_ ) 49 | { 50 | struct CompletionArgs_ModifyTask* pCompletionArgs_ModifyTask = pArgs_; 51 | struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB ); 52 | 53 | printf("CompletionFunctionPreComplete_ModifyDependentTask for run %u running on thread %u\n", 54 | pCompletionArgs_ModifyTask->run, threadNum_ ); 55 | 56 | // in this function we can modify the parameters of any task which depends on this CompletionFunction 57 | // pre complete functions should not be used to delete the current CompletionAction, for that use PostComplete functions 58 | paramsTaskNext.setSize = 10; // modify the set size of the next task - for example this could be based on output from previous task 59 | enkiSetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB, paramsTaskNext ); 60 | 61 | free( pCompletionArgs_ModifyTask ); 62 | } 63 | 64 | 65 | void CompletionFunctionPostComplete_DeleteTask( void* pArgs_, uint32_t threadNum_ ) 66 | { 67 | struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTask = pArgs_; 68 | 69 | printf("CompletionFunctionPostComplete_DeleteTask for run %u running on thread %u\n", 70 | pCompletionArgs_DeleteTask->run, threadNum_ ); 71 | 72 | // can free memory in post complete 73 | 74 | // note must delete a dependency before you delete the dependency task and the task to run on completion 75 | if( pCompletionArgs_DeleteTask->pDependency ) 76 | { 77 | enkiDeleteDependency( pETS, pCompletionArgs_DeleteTask->pDependency ); 78 | } 79 | 80 | free( enkiGetParamsTaskSet( pCompletionArgs_DeleteTask->pTask ).pArgs ); 81 | enkiDeleteTaskSet( pETS, pCompletionArgs_DeleteTask->pTask ); 82 | 83 | enkiDeleteCompletionAction( pETS, pCompletionArgs_DeleteTask->pCompletionAction ); 84 | 85 | // safe to free our own args in this example as no other function dereferences them 86 | free( pCompletionArgs_DeleteTask ); 87 | } 88 | 89 | void TaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ) 90 | { 91 | (void)start_; (void)end_; 92 | struct TaskSetArgs* pTaskSetArgs = pArgs_; 93 | struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pTaskSetArgs->pTask ); 94 | if( 0 == start_ ) 95 | { 96 | // for clarity in this example we only output one printf per taskset func called, but would normally loop from start_ to end_ doing work 97 | printf("Task %s for run %u running on thread %u has set size %u\n", pTaskSetArgs->name, pTaskSetArgs->run, threadnum_, paramsTaskNext.setSize); 98 | } 99 | 100 | // A TastSetFunction is not a safe place to free it's own pArgs_ as when the setSize > 1 there may be multiple 101 | // calls to this function with the same pArgs_ 102 | } 103 | 104 | 105 | int main(int argc, const char * argv[]) 106 | { 107 | // This examples shows CompletionActions used to modify a following tasks parameters and free allocations 108 | // Task Graph for this example (with names shortened to fit on screen): 109 | // 110 | // pTaskSetA 111 | // ->pCompletionActionA-PreFunc-PostFunc 112 | // ->pTaskSetB 113 | // ->pCompletionActionB-(no PreFunc)-PostFunc 114 | // 115 | // Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA 116 | // so cannot be modified. 117 | 118 | struct enkiTaskSet* pTaskSetA; 119 | struct enkiCompletionAction* pCompletionActionA; 120 | struct enkiTaskSet* pTaskSetB; 121 | struct enkiCompletionAction* pCompletionActionB; 122 | struct TaskSetArgs* pTaskSetArgsA; 123 | struct CompletionArgs_ModifyTask* pCompletionArgsA; 124 | struct enkiParamsCompletionAction paramsCompletionActionA; 125 | struct TaskSetArgs* pTaskSetArgsB; 126 | struct enkiDependency* pDependencyOfTaskSetBOnCompletionActionA; 127 | struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskA; 128 | struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskB; 129 | struct enkiParamsCompletionAction paramsCompletionActionB; 130 | int run; 131 | 132 | pETS = enkiNewTaskScheduler(); 133 | enkiInitTaskScheduler( pETS ); 134 | 135 | for( run=0; run<10; ++run ) 136 | { 137 | // Create all this runs tasks and completion actions 138 | pTaskSetA = enkiCreateTaskSet( pETS, TaskSetFunc ); 139 | pCompletionActionA = enkiCreateCompletionAction( pETS, 140 | CompletionFunctionPreComplete_ModifyDependentTask, 141 | CompletionFunctionPostComplete_DeleteTask ); 142 | pTaskSetB = enkiCreateTaskSet( pETS, TaskSetFunc ); 143 | pCompletionActionB = enkiCreateCompletionAction( pETS, 144 | NULL, 145 | CompletionFunctionPostComplete_DeleteTask ); 146 | 147 | // Set args for TaskSetA 148 | pTaskSetArgsA = malloc(sizeof(struct TaskSetArgs)); 149 | pTaskSetArgsA->run = run; 150 | pTaskSetArgsA->pTask = pTaskSetA; 151 | pTaskSetArgsA->name = "A"; 152 | enkiSetArgsTaskSet( pTaskSetA, pTaskSetArgsA ); 153 | 154 | // Set args for CompletionActionA, and make dependent on TaskSetA through pDependency 155 | pCompletionArgsA = malloc(sizeof(struct CompletionArgs_ModifyTask)); 156 | pCompletionArgsA->pTaskB = pTaskSetB; 157 | pCompletionArgsA->run = run; 158 | pCompletionArgs_DeleteTaskA = malloc(sizeof(struct CompletionArgs_DeleteTask)); 159 | pCompletionArgs_DeleteTaskA->pTask = pTaskSetA; 160 | pCompletionArgs_DeleteTaskA->pCompletionAction = pCompletionActionA; 161 | pCompletionArgs_DeleteTaskA->pDependency = NULL; 162 | pCompletionArgs_DeleteTaskA->run = run; 163 | 164 | paramsCompletionActionA = enkiGetParamsCompletionAction( pCompletionActionA ); 165 | paramsCompletionActionA.pArgsPreComplete = pCompletionArgsA; 166 | paramsCompletionActionA.pArgsPostComplete = pCompletionArgs_DeleteTaskA; 167 | paramsCompletionActionA.pDependency = enkiGetCompletableFromTaskSet( pTaskSetA ); 168 | enkiSetParamsCompletionAction( pCompletionActionA, paramsCompletionActionA ); 169 | 170 | 171 | // Set args for TaskSetB 172 | pTaskSetArgsB = malloc(sizeof(struct TaskSetArgs)); 173 | pTaskSetArgsB->run = run; 174 | pTaskSetArgsB->pTask = pTaskSetB; 175 | pTaskSetArgsB->name = "B"; 176 | enkiSetArgsTaskSet( pTaskSetB, pTaskSetArgsB ); 177 | 178 | // TaskSetB depends on pCompletionActionA 179 | pDependencyOfTaskSetBOnCompletionActionA = enkiCreateDependency( pETS ); 180 | enkiSetDependency( pDependencyOfTaskSetBOnCompletionActionA, 181 | enkiGetCompletableFromCompletionAction( pCompletionActionA ), 182 | enkiGetCompletableFromTaskSet( pTaskSetB ) ); 183 | 184 | // Set args for CompletionActionB, and make dependent on TaskSetB through pDependency 185 | pCompletionArgs_DeleteTaskB = malloc(sizeof(struct CompletionArgs_DeleteTask)); 186 | pCompletionArgs_DeleteTaskB->pTask = pTaskSetB; 187 | pCompletionArgs_DeleteTaskB->pDependency = pDependencyOfTaskSetBOnCompletionActionA; 188 | pCompletionArgs_DeleteTaskB->pCompletionAction = pCompletionActionB; 189 | pCompletionArgs_DeleteTaskB->run = run; 190 | 191 | paramsCompletionActionB = enkiGetParamsCompletionAction( pCompletionActionB ); 192 | paramsCompletionActionB.pArgsPreComplete = NULL; // pCompletionActionB does not have a PreComplete function 193 | paramsCompletionActionB.pArgsPostComplete = pCompletionArgs_DeleteTaskB; 194 | paramsCompletionActionB.pDependency = enkiGetCompletableFromTaskSet( pTaskSetB ); 195 | enkiSetParamsCompletionAction( pCompletionActionB, paramsCompletionActionB ); 196 | 197 | 198 | // To launch all, we only add the first TaskSet 199 | enkiAddTaskSet( pETS, pTaskSetA ); 200 | } 201 | enkiWaitForAll( pETS ); 202 | 203 | enkiDeleteTaskScheduler( pETS ); 204 | } 205 | -------------------------------------------------------------------------------- /src/LockLessMultiReadPipe.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #ifndef ENKI_ASSERT 26 | #include 27 | #define ENKI_ASSERT(x) assert(x) 28 | #endif 29 | 30 | namespace enki 31 | { 32 | // LockLessMultiReadPipe - Single writer, multiple reader thread safe pipe using (semi) lockless programming 33 | // Readers can only read from the back of the pipe 34 | // The single writer can write to the front of the pipe, and read from both ends (a writer can be a reader) 35 | // for many of the principles used here, see http://msdn.microsoft.com/en-us/library/windows/desktop/ee418650(v=vs.85).aspx 36 | // Note: using log2 sizes so we do not need to clamp (multi-operation) 37 | // T is the contained type 38 | // Note this is not true lockless as the use of flags as a form of lock state. 39 | template class LockLessMultiReadPipe 40 | { 41 | public: 42 | LockLessMultiReadPipe(); 43 | ~LockLessMultiReadPipe() {} 44 | 45 | // ReaderTryReadBack returns false if we were unable to read 46 | // This is thread safe for both multiple readers and the writer 47 | bool ReaderTryReadBack( T* pOut ); 48 | 49 | // WriterTryReadFront returns false if we were unable to read 50 | // This is thread safe for the single writer, but should not be called by readers 51 | bool WriterTryReadFront( T* pOut ); 52 | 53 | // WriterTryWriteFront returns false if we were unable to write 54 | // This is thread safe for the single writer, but should not be called by readers 55 | bool WriterTryWriteFront( const T& in ); 56 | 57 | // IsPipeEmpty() is a utility function, not intended for general use 58 | // Should only be used very prudently. 59 | bool IsPipeEmpty() const 60 | { 61 | return 0 == m_WriteIndex.load( std::memory_order_relaxed ) - m_ReadCount.load( std::memory_order_relaxed ); 62 | } 63 | 64 | void Clear() 65 | { 66 | m_WriteIndex = 0; 67 | m_ReadIndex = 0; 68 | m_ReadCount = 0; 69 | memset( (void*)m_Flags, 0, sizeof( m_Flags ) ); 70 | } 71 | 72 | private: 73 | const static uint32_t ms_cSize = ( 1 << cSizeLog2 ); 74 | const static uint32_t ms_cIndexMask = ms_cSize - 1; 75 | const static uint32_t FLAG_INVALID = 0xFFFFFFFF; // 32bit for CAS 76 | const static uint32_t FLAG_CAN_WRITE = 0x00000000; // 32bit for CAS 77 | const static uint32_t FLAG_CAN_READ = 0x11111111; // 32bit for CAS 78 | 79 | T m_Buffer[ ms_cSize ]; 80 | 81 | // read and write indexes allow fast access to the pipe, but actual access 82 | // controlled by the access flags. 83 | std::atomic m_WriteIndex; 84 | std::atomic m_ReadCount; 85 | std::atomic m_Flags[ ms_cSize ]; 86 | std::atomic m_ReadIndex; 87 | }; 88 | 89 | template inline 90 | LockLessMultiReadPipe::LockLessMultiReadPipe() 91 | : m_WriteIndex(0) 92 | , m_ReadCount(0) 93 | , m_ReadIndex(0) 94 | { 95 | ENKI_ASSERT( cSizeLog2 < 32 ); 96 | memset( (void*)m_Flags, 0, sizeof( m_Flags ) ); 97 | } 98 | 99 | template inline 100 | bool LockLessMultiReadPipe::ReaderTryReadBack( T* pOut ) 101 | { 102 | 103 | uint32_t actualReadIndex; 104 | uint32_t readCount = m_ReadCount.load( std::memory_order_relaxed ); 105 | 106 | // We get hold of read index for consistency 107 | // and do first pass starting at read count 108 | uint32_t readIndexToUse = readCount; 109 | while(true) 110 | { 111 | 112 | uint32_t writeIndex = m_WriteIndex.load( std::memory_order_relaxed ); 113 | // power of two sizes ensures we can use a simple calc without modulus 114 | uint32_t numInPipe = writeIndex - readCount; 115 | if( 0 == numInPipe ) 116 | { 117 | return false; 118 | } 119 | if( readIndexToUse >= writeIndex ) 120 | { 121 | readIndexToUse = m_ReadIndex.load( std::memory_order_relaxed ); 122 | } 123 | 124 | // power of two sizes ensures we can perform AND for a modulus 125 | actualReadIndex = readIndexToUse & ms_cIndexMask; 126 | 127 | // Multiple potential readers mean we should check if the data is valid, 128 | // using an atomic compare exchange 129 | uint32_t previous = FLAG_CAN_READ; 130 | bool bSuccess = m_Flags[ actualReadIndex ].compare_exchange_strong( previous, FLAG_INVALID, std::memory_order_acq_rel, std::memory_order_relaxed ); 131 | if( bSuccess ) 132 | { 133 | break; 134 | } 135 | ++readIndexToUse; 136 | 137 | // Update read count 138 | readCount = m_ReadCount.load( std::memory_order_relaxed ); 139 | } 140 | 141 | // we update the read index using an atomic add, as we've only read one piece of data. 142 | // this ensure consistency of the read index, and the above loop ensures readers 143 | // only read from unread data 144 | m_ReadCount.fetch_add(1, std::memory_order_relaxed ); 145 | 146 | // now read data, ensuring we do so after above reads & CAS 147 | *pOut = m_Buffer[ actualReadIndex ]; 148 | 149 | m_Flags[ actualReadIndex ].store( FLAG_CAN_WRITE, std::memory_order_release ); 150 | 151 | return true; 152 | } 153 | 154 | template inline 155 | bool LockLessMultiReadPipe::WriterTryReadFront( T* pOut ) 156 | { 157 | uint32_t writeIndex = m_WriteIndex.load( std::memory_order_relaxed ); 158 | uint32_t frontReadIndex = writeIndex; 159 | 160 | // Multiple potential readers mean we should check if the data is valid, 161 | // using an atomic compare exchange - which acts as a form of lock (so not quite lockless really). 162 | uint32_t actualReadIndex = 0; 163 | while(true) 164 | { 165 | uint32_t readCount = m_ReadCount.load( std::memory_order_relaxed ); 166 | // power of two sizes ensures we can use a simple calc without modulus 167 | uint32_t numInPipe = writeIndex - readCount; 168 | if( 0 == numInPipe ) 169 | { 170 | m_ReadIndex.store( readCount, std::memory_order_release ); 171 | return false; 172 | } 173 | --frontReadIndex; 174 | actualReadIndex = frontReadIndex & ms_cIndexMask; 175 | uint32_t previous = FLAG_CAN_READ; 176 | bool success = m_Flags[ actualReadIndex ].compare_exchange_strong( previous, FLAG_INVALID, std::memory_order_acq_rel, std::memory_order_relaxed ); 177 | if( success ) 178 | { 179 | break; 180 | } 181 | else if( m_ReadIndex.load( std::memory_order_acquire ) >= frontReadIndex ) 182 | { 183 | return false; 184 | } 185 | } 186 | 187 | // now read data, ensuring we do so after above reads & CAS 188 | *pOut = m_Buffer[ actualReadIndex ]; 189 | 190 | m_Flags[ actualReadIndex ].store( FLAG_CAN_WRITE, std::memory_order_relaxed ); 191 | 192 | m_WriteIndex.store(writeIndex-1, std::memory_order_relaxed); 193 | return true; 194 | } 195 | 196 | 197 | template inline 198 | bool LockLessMultiReadPipe::WriterTryWriteFront( const T& in ) 199 | { 200 | // The writer 'owns' the write index, and readers can only reduce 201 | // the amount of data in the pipe. 202 | // We get hold of both values for consistency and to reduce false sharing 203 | // impacting more than one access 204 | uint32_t writeIndex = m_WriteIndex; 205 | 206 | // power of two sizes ensures we can perform AND for a modulus 207 | uint32_t actualWriteIndex = writeIndex & ms_cIndexMask; 208 | 209 | // a reader may still be reading this item, as there are multiple readers 210 | if( m_Flags[ actualWriteIndex ].load(std::memory_order_acquire) != FLAG_CAN_WRITE ) 211 | { 212 | return false; // still being read, so have caught up with tail. 213 | } 214 | 215 | // as we are the only writer we can update the data without atomics 216 | // whilst the write index has not been updated 217 | m_Buffer[ actualWriteIndex ] = in; 218 | m_Flags[ actualWriteIndex ].store( FLAG_CAN_READ, std::memory_order_release ); 219 | 220 | m_WriteIndex.fetch_add(1, std::memory_order_relaxed); 221 | return true; 222 | } 223 | 224 | 225 | // Lockless multiwriter intrusive list 226 | // Type T must implement T* volatile pNext; 227 | template class LocklessMultiWriteIntrusiveList 228 | { 229 | 230 | std::atomic pHead; 231 | T tail; 232 | public: 233 | LocklessMultiWriteIntrusiveList() : pHead( &tail ) 234 | { 235 | tail.pNext = NULL; 236 | } 237 | 238 | bool IsListEmpty() const 239 | { 240 | return pHead == &tail; 241 | } 242 | 243 | // Add - safe to perform from any thread 244 | void WriterWriteFront( T* pNode_ ) 245 | { 246 | ENKI_ASSERT( pNode_ ); 247 | pNode_->pNext = NULL; 248 | T* pPrev = pHead.exchange( pNode_ ); 249 | pPrev->pNext = pNode_; 250 | } 251 | 252 | // Remove - only thread safe for owner 253 | T* ReaderReadBack() 254 | { 255 | T* pTailPlus1 = tail.pNext; 256 | if( pTailPlus1 ) 257 | { 258 | T* pTailPlus2 = pTailPlus1->pNext; 259 | if( pTailPlus2 ) 260 | { 261 | //not head 262 | tail.pNext = pTailPlus2; 263 | } 264 | else 265 | { 266 | tail.pNext = NULL; 267 | T* pCompare = pTailPlus1; // we need preserve pTailPlus1 as compare will alter it on failure 268 | // pTailPlus1 is the head, attempt swap with tail 269 | if( !pHead.compare_exchange_strong( pCompare, &tail ) ) 270 | { 271 | // pCompare receives the revised pHead on failure. 272 | // pTailPlus1 is no longer the head, so pTailPlus1->pNext should be non NULL 273 | while( (T*)NULL == pTailPlus1->pNext ) {;} // wait for pNext to be updated as head may have just changed. 274 | tail.pNext = pTailPlus1->pNext.load(); 275 | pTailPlus1->pNext = NULL; 276 | } 277 | } 278 | } 279 | return pTailPlus1; 280 | } 281 | }; 282 | 283 | } 284 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Support development of enkiTS through [Github Sponsors](https://github.com/sponsors/dougbinks) or [Patreon](https://www.patreon.com/enkisoftware) 2 | 3 | [](https://github.com/sponsors/dougbinks) [Become a Patron](https://www.patreon.com/enkisoftware) 4 | 5 | ![enkiTS Logo](https://github.com/dougbinks/images/blob/master/enkiTS_logo_no_padding.png?raw=true) 6 | # enkiTS 7 | | [Master branch](https://github.com/dougbinks/enkiTS/) | [Dev branch](https://github.com/dougbinks/enkiTS/tree/dev) | 8 | | --- | --- | 9 | | [![Build Status for branch: master](https://github.com/dougbinks/enkiTS/actions/workflows/build.yml/badge.svg)](https://github.com/dougbinks/enkiTS/actions) | [![Build Status for branch: dev](https://github.com/dougbinks/enkiTS/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/dougbinks/enkiTS/actions) | 10 | 11 | ## enki Task Scheduler 12 | 13 | A permissively licensed C and C++ Task Scheduler for creating parallel programs. Requires C++11 support. 14 | 15 | The primary goal of enkiTS is to help developers create programs which handle both data and task level parallelism to utilize the full performance of multicore CPUs, whilst being lightweight (only a small amount of code) and easy to use. 16 | 17 | * [C++ API via src/TaskScheduler.h](src/TaskScheduler.h) 18 | * [C API via src/TaskScheduler_c.h](src/TaskScheduler_c.h) 19 | 20 | enkiTS was developed for, and is used in [enkisoftware](http://www.enkisoftware.com/)'s Avoyd codebase. 21 | 22 | ## Platforms 23 | 24 | - Windows, Linux, Mac OS, Android (should work on iOS) 25 | - x64 & x86, ARM 26 | 27 | enkiTS is primarily developed on x64 and x86 Intel architectures on MS Windows, with well tested support for Linux and somewhat less frequently tested support on Mac OS and ARM Android. 28 | 29 | ## Examples 30 | 31 | Several examples exist in the [example folder](https://github.com/dougbinks/enkiTS/tree/master/example). 32 | 33 | For further examples, see https://github.com/dougbinks/enkiTSExamples 34 | 35 | ## Building 36 | 37 | Building enkiTS is simple, just add the files in enkiTS/src to your build system (_c.* files can be ignored if you only need C++ interface), and add enkiTS/src to your include path. Unix / Linux builds will likely require the pthreads library. 38 | 39 | For C++ 40 | 41 | - Use `#include "TaskScheduler.h"` 42 | - Add enkiTS/src to your include path 43 | - Compile / Add to project: 44 | - `TaskScheduler.cpp` 45 | - Unix / Linux builds will likely require the pthreads library. 46 | 47 | For C 48 | 49 | - Use `#include "TaskScheduler_c.h"` 50 | - Add enkiTS/src to your include path 51 | - Compile / Add to project: 52 | - `TaskScheduler.cpp` 53 | - `TaskScheduler_c.cpp` 54 | - Unix / Linux builds will likely require the pthreads library. 55 | 56 | For cmake, on Windows / Mac OS X / Linux with cmake installed, open a prompt in the enkiTS directory and: 57 | 58 | 1. `mkdir build` 59 | 1. `cd build` 60 | 1. `cmake ..` 61 | 1. either run `make all` or for Visual Studio open `enkiTS.sln` 62 | 63 | ## Project Features 64 | 65 | 1. *Lightweight* - enkiTS is designed to be lean so you can use it anywhere easily, and understand it. 66 | 1. *Fast, then scalable* - enkiTS is designed for consumer devices first, so performance on a low number of threads is important, followed by scalability. 67 | 1. *Braided parallelism* - enkiTS can issue tasks from another task as well as from the thread which created the Task System, and has a simple task interface for both data parallel and task parallelism. 68 | 1. *Up-front Allocation friendly* - enkiTS is designed for zero allocations during scheduling. 69 | 1. *Can pin tasks to a given thread* - enkiTS can schedule a task which will only be run on the specified thread. 70 | 1. *Can set task priorities* - Up to 5 task priorities can be configured via define ENKITS_TASK_PRIORITIES_NUM (defaults to 3). Higher priority tasks are run before lower priority ones. 71 | 1. *Can register external threads to use with enkiTS* - Can configure enkiTS with numExternalTaskThreads which can be registered to use with the enkiTS API. 72 | 1. *Custom allocator API* - can configure enkiTS with custom allocators, see [example/CustomAllocator.cpp](example/CustomAllocator.cpp) and [example/CustomAllocator_c.c](example/CustomAllocator_c.c). 73 | 1. *Dependencies* - can set dependendencies between tasks see [example/Dependencies.cpp](example/Dependencies.cpp) and [example/Dependencies_c.c](example/Dependencies_c.c). 74 | 1. *Completion Actions* - can perform an action on task completion. This avoids the expensive action of adding the task to the scheduler, and can be used to safely delete a completed task. See [example/CompletionAction.cpp](example/CompletionAction.cpp) and [example/CompletionAction_c.c](example/CompletionAction_c.c) 75 | 1. **NEW** *Can wait for pinned tasks* - Can wait for pinned tasks, useful for creating IO threads which do no other work. See [example/WaitForNewPinnedTasks.cpp](example/WaitForNewPinnedTasks.cpp) and [example/WaitForNewPinnedTasks_c.c](example/WaitForNewPinnedTasks_c.c). 76 | 77 | ## Installing 78 | 79 | I recommend using enkiTS directly from source in each project rather than installing it for system wide use. However enkiTS' cmake script can also be used to install the library 80 | if the `ENKITS_INSTALL` cmake variable is set to `ON` (it defaults to `OFF`). 81 | 82 | When installed the header files are installed in a subdirectory of the include path, `include/enkiTS` to ensure that they do not conflict with header files from other packages. 83 | When building applications either ensure this is part of the `INCLUDE_PATH` variable or ensure that enkiTS is in the header path in the source files, for example use `#include "enkiTS/TaskScheduler.h"` instead of `#include "TaskScheduler.h"`. 84 | 85 | ## Using enkiTS 86 | 87 | ### C++ usage 88 | - full example in [example/ParallelSum.cpp](example/ParallelSum.cpp) 89 | - C example in [example/ParallelSum_c.c](example/ParallelSum_c.c) 90 | ```C 91 | #include "TaskScheduler.h" 92 | 93 | enki::TaskScheduler g_TS; 94 | 95 | // define a task set, can ignore range if we only do one thing 96 | struct ParallelTaskSet : enki::ITaskSet { 97 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override { 98 | // do something here, can issue tasks with g_TS 99 | } 100 | }; 101 | 102 | int main(int argc, const char * argv[]) { 103 | g_TS.Initialize(); 104 | ParallelTaskSet task; // default constructor has a set size of 1 105 | g_TS.AddTaskSetToPipe( &task ); 106 | 107 | // wait for task set (running tasks if they exist) 108 | // since we've just added it and it has no range we'll likely run it. 109 | g_TS.WaitforTask( &task ); 110 | return 0; 111 | } 112 | ``` 113 | 114 | ### C++ 11 lambda usage 115 | - full example in [example/LambdaTask.cpp](example/LambdaTask.cpp) 116 | ```C 117 | #include "TaskScheduler.h" 118 | 119 | enki::TaskScheduler g_TS; 120 | 121 | int main(int argc, const char * argv[]) { 122 | g_TS.Initialize(); 123 | 124 | enki::TaskSet task( 1, []( enki::TaskSetPartition range_, uint32_t threadnum_ ) { 125 | // do something here 126 | } ); 127 | 128 | g_TS.AddTaskSetToPipe( &task ); 129 | g_TS.WaitforTask( &task ); 130 | return 0; 131 | } 132 | ``` 133 | 134 | ### Task priorities usage in C++ 135 | - full example in [example/Priorities.cpp](example/Priorities.cpp) 136 | - C example in [example/Priorities_c.c](example/Priorities_c.c) 137 | ```C 138 | // See full example in Priorities.cpp 139 | #include "TaskScheduler.h" 140 | 141 | enki::TaskScheduler g_TS; 142 | 143 | struct ExampleTask : enki::ITaskSet 144 | { 145 | ExampleTask( ) { m_SetSize = size_; } 146 | 147 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override { 148 | // See full example in Priorities.cpp 149 | } 150 | }; 151 | 152 | 153 | // This example demonstrates how to run a long running task alongside tasks 154 | // which must complete as early as possible using priorities. 155 | int main(int argc, const char * argv[]) 156 | { 157 | g_TS.Initialize(); 158 | 159 | ExampleTask lowPriorityTask( 10 ); 160 | lowPriorityTask.m_Priority = enki::TASK_PRIORITY_LOW; 161 | 162 | ExampleTask highPriorityTask( 1 ); 163 | highPriorityTask.m_Priority = enki::TASK_PRIORITY_HIGH; 164 | 165 | g_TS.AddTaskSetToPipe( &lowPriorityTask ); 166 | for( int task = 0; task < 10; ++task ) 167 | { 168 | // run high priority tasks 169 | g_TS.AddTaskSetToPipe( &highPriorityTask ); 170 | 171 | // wait for task but only run tasks of the same priority or higher on this thread 172 | g_TS.WaitforTask( &highPriorityTask, highPriorityTask.m_Priority ); 173 | } 174 | // wait for low priority task, run any tasks on this thread whilst waiting 175 | g_TS.WaitforTask( &lowPriorityTask ); 176 | 177 | return 0; 178 | } 179 | ``` 180 | 181 | ### Pinned Tasks usage in C++ 182 | - full example in [example/PinnedTask.cpp](example/PinnedTask.cpp) 183 | - C example in [example/PinnedTask_c.c](example/PinnedTask_c.c) 184 | ```C 185 | #include "TaskScheduler.h" 186 | 187 | enki::TaskScheduler g_TS; 188 | 189 | // define a task set, can ignore range if we only do one thing 190 | struct PinnedTask : enki::IPinnedTask { 191 | void Execute() override { 192 | // do something here, can issue tasks with g_TS 193 | } 194 | }; 195 | 196 | int main(int argc, const char * argv[]) { 197 | g_TS.Initialize(); 198 | PinnedTask task; //default constructor sets thread for pinned task to 0 (main thread) 199 | g_TS.AddPinnedTask( &task ); 200 | 201 | // RunPinnedTasks must be called on main thread to run any pinned tasks for that thread. 202 | // Tasking threads automatically do this in their task loop. 203 | g_TS.RunPinnedTasks(); 204 | 205 | // wait for task set (running tasks if they exist) 206 | // since we've just added it and it has no range we'll likely run it. 207 | g_TS.WaitforTask( &task ); 208 | return 0; 209 | } 210 | ``` 211 | 212 | ### Dependency usage in C++ 213 | - full example in [example/Dependencies.cpp](example/Dependencies.cpp) 214 | - C example in [example/Dependencies_c.c](example/Dependencies_c.c) 215 | ```C 216 | #include "TaskScheduler.h" 217 | 218 | enki::TaskScheduler g_TS; 219 | 220 | // define a task set, can ignore range if we only do one thing 221 | struct TaskA : enki::ITaskSet { 222 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override { 223 | // do something here, can issue tasks with g_TS 224 | } 225 | }; 226 | 227 | struct TaskB : enki::ITaskSet { 228 | enki::Dependency m_Dependency; 229 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override { 230 | // do something here, can issue tasks with g_TS 231 | } 232 | }; 233 | 234 | int main(int argc, const char * argv[]) { 235 | g_TS.Initialize(); 236 | 237 | // set dependencies once (can set more than one if needed). 238 | TaskA taskA; 239 | TaskB taskB; 240 | taskB.SetDependency( taskB.m_Dependency, &taskA ); 241 | 242 | g_TS.AddTaskSetToPipe( &taskA ); // add first task 243 | g_TS.WaitforTask( &taskB ); // wait for last 244 | return 0; 245 | } 246 | ``` 247 | 248 | ### External task thread usage in C++ 249 | - full example in [example/ExternalTaskThread.cpp](example/ExternalTaskThread.cpp) 250 | - C example in [example/ExternalTaskThread_c.c](example/ExternalTaskThread_c.c) 251 | ```C 252 | #include "TaskScheduler.h" 253 | 254 | enki::TaskScheduler g_TS; 255 | struct ParallelTaskSet : ITaskSet 256 | { 257 | void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override { 258 | // Do something 259 | } 260 | }; 261 | 262 | void threadFunction() 263 | { 264 | g_TS.RegisterExternalTaskThread(); 265 | 266 | // sleep for a while instead of doing something such as file IO 267 | std::this_thread::sleep_for( std::chrono::milliseconds( num_ * 100 ) ); 268 | 269 | ParallelTaskSet task; 270 | g_TS.AddTaskSetToPipe( &task ); 271 | g_TS.WaitforTask( &task); 272 | 273 | g_TS.DeRegisterExternalTaskThread(); 274 | } 275 | 276 | int main(int argc, const char * argv[]) 277 | { 278 | enki::TaskSchedulerConfig config; 279 | config.numExternalTaskThreads = 1; // we have one extra external thread 280 | 281 | g_TS.Initialize( config ); 282 | 283 | std::thread exampleThread( threadFunction ); 284 | 285 | exampleThread.join(); 286 | 287 | return 0; 288 | } 289 | ``` 290 | 291 | ### WaitForPinnedTasks thread usage in C++ (useful for IO threads) 292 | - full example in [example/WaitForNewPinnedTasks.cpp](example/WaitForNewPinnedTasks.cpp) 293 | - C example in [example/WaitForNewPinnedTasks_c.c](example/WaitForNewPinnedTasks_c.c) 294 | ```C++ 295 | #include "TaskScheduler.h" 296 | 297 | enki::TaskScheduler g_TS; 298 | 299 | struct RunPinnedTaskLoopTask : enki::IPinnedTask 300 | { 301 | void Execute() override 302 | { 303 | while( !g_TS.GetIsShutdownRequested() ) 304 | { 305 | g_TS.WaitForNewPinnedTasks(); // this thread will 'sleep' until there are new pinned tasks 306 | g_TS.RunPinnedTasks(); 307 | } 308 | } 309 | }; 310 | 311 | struct PretendDoFileIO : enki::IPinnedTask 312 | { 313 | void Execute() override 314 | { 315 | // Do file IO 316 | } 317 | }; 318 | 319 | int main(int argc, const char * argv[]) 320 | { 321 | enki::TaskSchedulerConfig config; 322 | 323 | // In this example we create more threads than the hardware can run, 324 | // because the IO thread will spend most of it's time idle or blocked 325 | // and therefore not scheduled for CPU time by the OS 326 | config.numTaskThreadsToCreate += 1; 327 | 328 | g_TS.Initialize( config ); 329 | 330 | // in this example we place our IO threads at the end 331 | RunPinnedTaskLoopTask runPinnedTaskLoopTasks; 332 | runPinnedTaskLoopTasks.threadNum = g_TS.GetNumTaskThreads() - 1; 333 | g_TS.AddPinnedTask( &runPinnedTaskLoopTasks ); 334 | 335 | // Send pretend file IO task to external thread FILE_IO 336 | PretendDoFileIO pretendDoFileIO; 337 | pretendDoFileIO.threadNum = runPinnedTaskLoopTasks.threadNum; 338 | g_TS.AddPinnedTask( &pretendDoFileIO ); 339 | 340 | // ensure runPinnedTaskLoopTasks complete by explicitly calling shutdown 341 | g_TS.WaitforAllAndShutdown(); 342 | 343 | return 0; 344 | } 345 | ``` 346 | 347 | 348 | ## Bindings 349 | 350 | - Odin [enkiTS Odin bindings](https://github.com/nadako/odin-enkiTS) by @nadako 351 | 352 | ## Deprecated 353 | 354 | [The C++98 compatible branch](https://github.com/dougbinks/enkiTS/tree/C++98) has been deprecated as I'm not aware of anyone needing it. 355 | 356 | The user thread versions are no longer being maintained as they are no longer in use. Similar functionality can be obtained with the externalTaskThreads 357 | * [User thread version on Branch UserThread](https://github.com/dougbinks/enkiTS/tree/UserThread) for running enkiTS on other tasking / threading systems, so it can be used as in other engines as well as standalone for example. 358 | * [C++ 11 version of user threads on Branch UserThread_C++11](https://github.com/dougbinks/enkiTS/tree/UserThread_C++11) 359 | 360 | ## Projects using enkiTS 361 | 362 | ### [Avoyd](https://www.avoyd.com) 363 | Avoyd is an abstract 6 degrees of freedom voxel game. enkiTS was developed for use in our [in-house engine powering Avoyd](https://www.enkisoftware.com/faq#engine). 364 | 365 | ![Avoyd screenshot](https://github.com/juliettef/Media/blob/main/Avoyd_2019-06-22_enkiTS_microprofile.jpg?raw=true) 366 | 367 | ### [Imogen](https://github.com/CedricGuillemet/Imogen) 368 | GPU/CPU Texture Generator 369 | 370 | ![Imogen screenshot](https://camo.githubusercontent.com/28347bc0c1627aa4f289e1b2b769afcb3a5de370/68747470733a2f2f692e696d6775722e636f6d2f7351664f3542722e706e67) 371 | 372 | ### [ToyPathRacer](https://github.com/aras-p/ToyPathTracer) 373 | Aras Pranckevičius' code for his series on [Daily Path Tracer experiments with various languages](https://aras-p.info/blog/2018/03/28/Daily-Pathtracer-Part-0-Intro/). 374 | 375 | ![ToyPathTracer screenshot](https://github.com/aras-p/ToyPathTracer/blob/main/Shots/screenshot.jpg?raw=true). 376 | 377 | ### [Mastering Graphics Programming with Vulkan](https://github.com/PacktPublishing/Mastering-Graphics-Programming-with-Vulkan) 378 | Marco Castorina and Gabriel Sassone's book on developing a modern rendering engine from first principles using the Vulkan API. enkiTS is used as the task library to distribute work across cores. 379 | 380 | ![Mastering Graphics Programming with Vulkan](https://static.packt-cdn.com/products/9781803244792/cover/smaller) 381 | 382 | ## License (zlib) 383 | 384 | Copyright (c) 2013-2020 Doug Binks 385 | 386 | This software is provided 'as-is', without any express or implied 387 | warranty. In no event will the authors be held liable for any damages 388 | arising from the use of this software. 389 | 390 | Permission is granted to anyone to use this software for any purpose, 391 | including commercial applications, and to alter it and redistribute it 392 | freely, subject to the following restrictions: 393 | 394 | 1. The origin of this software must not be misrepresented; you must not 395 | claim that you wrote the original software. If you use this software 396 | in a product, an acknowledgement in the product documentation would be 397 | appreciated but is not required. 398 | 2. Altered source versions must be plainly marked as such, and must not be 399 | misrepresented as being the original software. 400 | 3. This notice may not be removed or altered from any source distribution. 401 | -------------------------------------------------------------------------------- /example/TestAll.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler.h" 20 | #include "Timer.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace enki; 31 | 32 | 33 | 34 | 35 | 36 | TaskScheduler g_TS; 37 | uint32_t g_numTestsRun = 0; 38 | uint32_t g_numTestsSucceeded = 0; 39 | 40 | void RunTestFunction( const char* pTestFuncName_, std::function TestFunc ) 41 | { 42 | ++g_numTestsRun; 43 | fprintf(stdout, "\nRunning: Test %2u: %s...\n", g_numTestsRun, pTestFuncName_ ); 44 | bool bSuccess = TestFunc(); 45 | if( bSuccess ) 46 | { 47 | fprintf(stdout, "SUCCESS: Test %2u: %s.\n", g_numTestsRun, pTestFuncName_ ); 48 | ++g_numTestsSucceeded; 49 | } 50 | else 51 | { 52 | fprintf(stderr, "FAILURE: Test %2u: %s.\n", g_numTestsRun, pTestFuncName_ ); 53 | } 54 | } 55 | 56 | struct ParallelSumTaskSet : ITaskSet 57 | { 58 | struct Count 59 | { 60 | // prevent false sharing. 61 | uint64_t count; 62 | char cacheline[64]; 63 | }; 64 | Count* m_pPartialSums; 65 | uint32_t m_NumPartialSums; 66 | 67 | ParallelSumTaskSet( uint32_t size_ ) : m_pPartialSums(NULL), m_NumPartialSums(0) { m_SetSize = size_; } 68 | virtual ~ParallelSumTaskSet() 69 | { 70 | delete[] m_pPartialSums; 71 | } 72 | 73 | void Init( uint32_t numPartialSums_ ) 74 | { 75 | delete[] m_pPartialSums; 76 | m_NumPartialSums =numPartialSums_ ; 77 | m_pPartialSums = new Count[ m_NumPartialSums ]; 78 | memset( m_pPartialSums, 0, sizeof(Count)*m_NumPartialSums ); 79 | } 80 | 81 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 82 | { 83 | assert( m_pPartialSums && m_NumPartialSums ); 84 | uint64_t sum = m_pPartialSums[threadnum_].count; 85 | for( uint64_t i = range_.start; i < range_.end; ++i ) 86 | { 87 | sum += i + 1; 88 | } 89 | m_pPartialSums[threadnum_].count = sum; 90 | } 91 | 92 | }; 93 | 94 | struct ParallelReductionSumTaskSet : ITaskSet 95 | { 96 | ParallelSumTaskSet* m_pParallelSum; 97 | Dependency m_Dependency; 98 | uint64_t m_FinalSum; 99 | 100 | ParallelReductionSumTaskSet( ParallelSumTaskSet* pParallelSum_ ) : m_pParallelSum( pParallelSum_ ), m_Dependency( pParallelSum_, this ), m_FinalSum(0) 101 | { 102 | } 103 | 104 | void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override 105 | { 106 | for( uint32_t i = 0; i < m_pParallelSum->m_NumPartialSums; ++i ) 107 | { 108 | m_FinalSum += m_pParallelSum->m_pPartialSums[i].count; 109 | } 110 | } 111 | }; 112 | 113 | void threadFunction( uint32_t setSize_, bool* pbRegistered_, uint64_t* pSumParallel_ ) 114 | { 115 | *pbRegistered_ = g_TS.RegisterExternalTaskThread(); 116 | if( *pbRegistered_ ) 117 | { 118 | ParallelSumTaskSet parallelSumTask( setSize_ ); 119 | parallelSumTask.Init( g_TS.GetNumTaskThreads() ); 120 | ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask ); 121 | 122 | g_TS.AddTaskSetToPipe( ¶llelSumTask ); 123 | g_TS.WaitforTask( ¶llelReductionSumTaskSet ); 124 | 125 | g_TS.DeRegisterExternalTaskThread(); 126 | *pSumParallel_ = parallelReductionSumTaskSet.m_FinalSum; 127 | } 128 | } 129 | 130 | struct PinnedTask : IPinnedTask 131 | { 132 | PinnedTask() 133 | : IPinnedTask( enki::GetNumHardwareThreads() - 1 ) // set pinned thread to 0 134 | {} 135 | virtual void Execute() 136 | { 137 | threadRunOn = g_TS.GetThreadNum(); 138 | } 139 | uint32_t threadRunOn = 0; 140 | }; 141 | 142 | 143 | struct TestPriorities : ITaskSet 144 | { 145 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 146 | { 147 | } 148 | }; 149 | 150 | struct CustomAllocData 151 | { 152 | const char* domainName; 153 | uint64_t totalAllocations; 154 | }; 155 | 156 | void* CustomAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ ) 157 | { 158 | CustomAllocData* data = (CustomAllocData*)userData_; 159 | data->totalAllocations += size_; 160 | return DefaultAllocFunc( align_, size_, userData_, file_, line_ ); 161 | }; 162 | 163 | void CustomFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ ) 164 | { 165 | CustomAllocData* data = (CustomAllocData*)userData_; 166 | data->totalAllocations -= size_; 167 | DefaultFreeFunc( ptr_, size_, userData_, file_, line_ ); 168 | }; 169 | 170 | std::atomic gs_DependencyCounter = {0}; 171 | 172 | struct TestDependenciesTaskSet : ITaskSet 173 | { 174 | int32_t m_Counter = 0; 175 | std::vector m_Dependencies; 176 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 177 | { 178 | m_Counter = gs_DependencyCounter.fetch_add(1); 179 | } 180 | }; 181 | 182 | struct TestDependenciesPinnedTask : IPinnedTask 183 | { 184 | int32_t m_Counter = 0; 185 | std::vector m_Dependencies; 186 | void Execute() override 187 | { 188 | m_Counter = gs_DependencyCounter.fetch_add(1); 189 | } 190 | }; 191 | 192 | struct TestDependenciesCompletable : ICompletable 193 | { 194 | std::vector m_Dependencies; 195 | }; 196 | 197 | int main(int argc, const char * argv[]) 198 | { 199 | fprintf( stdout,"\n---Running Tests----\n" ); 200 | 201 | enki::TaskSchedulerConfig baseConfig; 202 | fprintf( stdout,"System has %u hardware threads reported\n", baseConfig.numTaskThreadsToCreate + 1 ); 203 | if( 0 == baseConfig.numTaskThreadsToCreate ) 204 | { 205 | baseConfig.numTaskThreadsToCreate = 1; 206 | fprintf( stdout,"As only one hardware thread forcing enkiTS to use 2 threads\n"); 207 | } 208 | 209 | uint32_t setSize = 20 * 1024 * 1024; 210 | uint64_t sumSerial; 211 | 212 | // evaluate serial for test comparison with parallel runs 213 | ParallelSumTaskSet serialTask( setSize ); 214 | serialTask.Init( 1 ); 215 | TaskSetPartition range = { 0, setSize }; 216 | serialTask.ExecuteRange( range, 0 ); 217 | sumSerial = serialTask.m_pPartialSums[0].count; 218 | 219 | 220 | 221 | RunTestFunction( 222 | "Test Lots of TaskSets", 223 | [&]()->bool 224 | { 225 | g_TS.Initialize( baseConfig ); 226 | 227 | static constexpr uint32_t TASK_RANGE = 65*65; 228 | static constexpr uint32_t TASK_COUNT = 50; 229 | 230 | 231 | struct TaskSet : public enki::ITaskSet 232 | { 233 | TaskSet() : enki::ITaskSet(TASK_RANGE) {}; 234 | virtual void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 235 | { 236 | if( range_.start >= TASK_RANGE && range_.end > TASK_RANGE ) 237 | { 238 | countErrors.fetch_add(1); 239 | } 240 | } 241 | 242 | std::atomic countErrors{ 0 }; 243 | }; 244 | 245 | TaskSet tasks[TASK_COUNT]; 246 | 247 | for( uint32_t i = 0; i < TASK_COUNT; ++i ) 248 | { 249 | g_TS.AddTaskSetToPipe( &tasks[i] ); 250 | } 251 | 252 | g_TS.WaitforAll(); 253 | 254 | bool bSuccess = true; 255 | for( uint32_t i = 0; i < TASK_COUNT; ++i ) 256 | { 257 | if( tasks[i].countErrors.load( std::memory_order_relaxed ) > 0 ) 258 | { 259 | bSuccess = false; 260 | break; 261 | } 262 | } 263 | 264 | return bSuccess; 265 | } 266 | ); 267 | RunTestFunction( 268 | "Parallel Reduction Sum", 269 | [&]()->bool 270 | { 271 | g_TS.Initialize( baseConfig ); 272 | ParallelSumTaskSet parallelSumTask( setSize ); 273 | parallelSumTask.Init( g_TS.GetNumTaskThreads() ); 274 | ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask ); 275 | 276 | g_TS.AddTaskSetToPipe( ¶llelSumTask ); 277 | g_TS.WaitforTask( ¶llelReductionSumTaskSet ); 278 | 279 | fprintf( stdout,"\tParallelReductionSum: %" PRIu64 ", sumSerial: %" PRIu64 "\n", parallelReductionSumTaskSet.m_FinalSum, sumSerial ); 280 | return parallelReductionSumTaskSet.m_FinalSum == sumSerial; 281 | } ); 282 | 283 | RunTestFunction( 284 | "External Thread", 285 | [&]()->bool 286 | { 287 | enki::TaskSchedulerConfig config = baseConfig; 288 | config.numExternalTaskThreads = 1; 289 | bool bRegistered = false; 290 | uint64_t sumParallel = 0; 291 | g_TS.Initialize( config ); 292 | 293 | std::thread threads( threadFunction, setSize, &bRegistered, &sumParallel ); 294 | threads.join(); 295 | fprintf( stdout,"\tExternal thread sum: %" PRIu64 ", sumSerial: %" PRIu64 "\n", sumParallel, sumSerial ); 296 | if( !bRegistered ) 297 | { 298 | fprintf( stderr,"\tExternal thread did not register\n" ); 299 | return false; 300 | } 301 | if( sumParallel != sumSerial ) 302 | { 303 | return false; 304 | } 305 | return true; 306 | } ); 307 | 308 | RunTestFunction( 309 | "Pinned Task", 310 | [&]()->bool 311 | { 312 | g_TS.Initialize( baseConfig ); 313 | PinnedTask pinnedTask; 314 | g_TS.AddPinnedTask( &pinnedTask ); 315 | g_TS.WaitforTask( &pinnedTask ); 316 | fprintf( stdout,"\tPinned task ran on thread %u, requested thread %u\n", pinnedTask.threadRunOn, pinnedTask.threadNum ); 317 | return pinnedTask.threadRunOn == pinnedTask.threadNum; 318 | } ); 319 | 320 | RunTestFunction( 321 | "Priorities", 322 | [&]()->bool 323 | { 324 | // check priorities run in order by forcing single threaded execution 325 | enki::TaskSchedulerConfig config = baseConfig; 326 | config.numTaskThreadsToCreate = 0; 327 | g_TS.Initialize( config ); 328 | TestPriorities priorityTaskLow; 329 | priorityTaskLow.m_Priority = enki::TASK_PRIORITY_LOW; 330 | TestPriorities priorityTaskHigh; 331 | priorityTaskHigh.m_Priority = enki::TASK_PRIORITY_HIGH; 332 | g_TS.AddTaskSetToPipe( &priorityTaskLow ); 333 | g_TS.AddTaskSetToPipe( &priorityTaskHigh ); 334 | g_TS.WaitforTask( &priorityTaskHigh, priorityTaskHigh.m_Priority ); 335 | 336 | // WaitforTask should not have been run any task below high priority, 337 | // even though low priority task was added first 338 | if( priorityTaskLow.GetIsComplete() ) 339 | { 340 | return false; 341 | } 342 | 343 | g_TS.WaitforTask( &priorityTaskLow ); 344 | 345 | return true; 346 | } ); 347 | 348 | RunTestFunction( 349 | "Custom Allocator", 350 | [&]()->bool 351 | { 352 | enki::TaskSchedulerConfig config = baseConfig; 353 | config.customAllocator.alloc = CustomAllocFunc; 354 | config.customAllocator.free = CustomFreeFunc; 355 | CustomAllocData customAllocdata{ "enkITS", 0 }; 356 | config.customAllocator.userData = &customAllocdata; 357 | 358 | g_TS.Initialize( config ); 359 | uint64_t allocsAfterInit = customAllocdata.totalAllocations; 360 | fprintf( stdout,"\tenkiTS allocated bytes after init: %" PRIu64 "\n", customAllocdata.totalAllocations ); 361 | 362 | ParallelSumTaskSet parallelSumTask( setSize ); 363 | parallelSumTask.Init( g_TS.GetNumTaskThreads() ); 364 | ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask ); 365 | g_TS.AddTaskSetToPipe( ¶llelSumTask ); 366 | g_TS.WaitforTask( ¶llelReductionSumTaskSet ); 367 | 368 | fprintf( stdout,"\tenkiTS allocated bytes after running tasks: %" PRIu64 "\n", customAllocdata.totalAllocations ); 369 | if( customAllocdata.totalAllocations != allocsAfterInit ) 370 | { 371 | fprintf( stderr,"\tERROR: enkiTS allocated bytes during scheduling\n" ); 372 | return false; 373 | } 374 | g_TS.WaitforAllAndShutdown(); 375 | fprintf( stdout,"\tenkiTS allocated bytes after shutdown: %" PRIu64 "\n", customAllocdata.totalAllocations ); 376 | return customAllocdata.totalAllocations == 0; 377 | } ); 378 | 379 | RunTestFunction( 380 | "Dependencies", 381 | [&]()->bool 382 | { 383 | g_TS.Initialize( baseConfig ); 384 | 385 | TestDependenciesTaskSet taskSetA; 386 | 387 | TestDependenciesTaskSet taskSetBs[8]; 388 | for( auto& task : taskSetBs ) 389 | { 390 | task.SetDependenciesVec(task.m_Dependencies,{&taskSetA}); 391 | } 392 | 393 | TestDependenciesPinnedTask pinnedTaskC; 394 | pinnedTaskC.SetDependenciesVec(pinnedTaskC.m_Dependencies, taskSetBs); 395 | 396 | TestDependenciesTaskSet taskSetDs[8]; 397 | for( auto& task : taskSetDs ) 398 | { 399 | task.SetDependenciesVec(task.m_Dependencies,{&pinnedTaskC}); 400 | } 401 | TestDependenciesTaskSet taskSetEs[4]; 402 | for( auto& task : taskSetEs ) 403 | { 404 | task.SetDependenciesVec(task.m_Dependencies,taskSetDs); 405 | } 406 | 407 | TestDependenciesCompletable finalTask; 408 | finalTask.SetDependenciesVec( finalTask.m_Dependencies,taskSetEs); 409 | 410 | g_TS.AddTaskSetToPipe( &taskSetA ); 411 | g_TS.WaitforTask( &finalTask ); 412 | 413 | // check counters and complete status 414 | if( !taskSetA.GetIsComplete() ) 415 | { 416 | fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetA not complete\n"); 417 | return false; 418 | } 419 | 420 | int32_t lastCount = taskSetA.m_Counter; 421 | int32_t countCheck = lastCount; 422 | for( auto& task : taskSetBs ) 423 | { 424 | if( !task.GetIsComplete() ) 425 | { 426 | fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetBs not complete\n"); 427 | return false; 428 | } 429 | if( task.m_Counter < countCheck ) 430 | { 431 | fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", task.m_Counter, lastCount, __LINE__ ); 432 | return false; 433 | } 434 | lastCount = std::max( lastCount, task.m_Counter ); 435 | } 436 | countCheck = lastCount; 437 | if( !pinnedTaskC.GetIsComplete() ) 438 | { 439 | fprintf( stderr,"\tERROR: enkiTS dependencies issue pinnedTaskC not complete\n"); 440 | return false; 441 | } 442 | if( pinnedTaskC.m_Counter < countCheck ) 443 | { 444 | fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", pinnedTaskC.m_Counter, lastCount, __LINE__ ); 445 | return false; 446 | lastCount = std::max( lastCount, pinnedTaskC.m_Counter ); 447 | } 448 | countCheck = lastCount; 449 | for( auto& task : taskSetDs ) 450 | { 451 | if( !task.GetIsComplete() ) 452 | { 453 | fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetDs not complete\n"); 454 | return false; 455 | } 456 | if( task.m_Counter < countCheck ) 457 | { 458 | fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", task.m_Counter, lastCount, __LINE__ ); 459 | return false; 460 | } 461 | lastCount = std::max( lastCount, task.m_Counter ); 462 | } 463 | countCheck = lastCount; 464 | for( auto& task : taskSetEs ) 465 | { 466 | if( !task.GetIsComplete() ) 467 | { 468 | fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetEs not complete\n"); 469 | return false; 470 | } 471 | if( task.m_Counter < countCheck ) 472 | { 473 | fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", task.m_Counter, lastCount, __LINE__ ); 474 | return false; 475 | } 476 | lastCount = std::max( lastCount, task.m_Counter ); 477 | } 478 | g_TS.WaitforAllAndShutdown(); 479 | return true; 480 | } ); 481 | 482 | RunTestFunction( 483 | "WaitForNewPinnedTasks", 484 | [&]()->bool 485 | { 486 | enki::TaskSchedulerConfig config = baseConfig; 487 | config.numTaskThreadsToCreate += 1; 488 | g_TS.Initialize( config ); 489 | const uint32_t PINNED_ONLY_THREAD = g_TS.GetNumTaskThreads() - 1; 490 | 491 | LambdaPinnedTask waitTask( PINNED_ONLY_THREAD, []() 492 | { 493 | while( g_TS.GetIsWaitforAllCalled() ) 494 | { 495 | g_TS.WaitForNewPinnedTasks(); 496 | g_TS.RunPinnedTasks(); 497 | } 498 | } ); 499 | 500 | g_TS.AddPinnedTask( &waitTask ); 501 | 502 | PinnedTask pinnedTask; 503 | pinnedTask.threadNum = PINNED_ONLY_THREAD; 504 | g_TS.AddPinnedTask( &pinnedTask ); 505 | g_TS.WaitforTask( &pinnedTask ); 506 | fprintf( stdout,"\tPinned task ran on thread %u, requested thread %u\n", pinnedTask.threadRunOn, pinnedTask.threadNum ); 507 | if( pinnedTask.threadRunOn != pinnedTask.threadNum ) 508 | { 509 | return false; 510 | } 511 | 512 | g_TS.WaitforAll(); // force all tasks to end, waitTask should exit because we use GetIsWaitforAllCalled() 513 | 514 | g_TS.AddPinnedTask( &waitTask ); 515 | g_TS.AddPinnedTask( &pinnedTask ); 516 | g_TS.WaitforTask( &pinnedTask ); 517 | fprintf( stdout,"\tPinned task ran on thread %u, requested thread %u\n", pinnedTask.threadRunOn, pinnedTask.threadNum ); 518 | 519 | g_TS.WaitforAllAndShutdown(); 520 | 521 | return pinnedTask.threadRunOn == pinnedTask.threadNum; 522 | } ); 523 | 524 | RunTestFunction( 525 | "ShutdownNow", 526 | [&]()->bool 527 | { 528 | g_TS.Initialize( baseConfig ); 529 | ParallelSumTaskSet parallelSumTask( setSize ); 530 | parallelSumTask.Init( g_TS.GetNumTaskThreads() ); 531 | ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask ); 532 | 533 | g_TS.AddTaskSetToPipe( ¶llelSumTask ); 534 | g_TS.WaitforTask( ¶llelReductionSumTaskSet ); 535 | 536 | fprintf( stdout,"\tCalling ShutdownNow()..."); 537 | g_TS.ShutdownNow(); 538 | fprintf( stdout," ...completed.\n" ); 539 | return parallelReductionSumTaskSet.m_FinalSum == sumSerial; 540 | } ); 541 | 542 | fprintf( stdout, "\n%u Tests Run\n%u Tests Succeeded\n\n", g_numTestsRun, g_numTestsSucceeded ); 543 | if( g_numTestsRun == g_numTestsSucceeded ) 544 | { 545 | fprintf( stdout, "All tests SUCCEEDED\n" ); 546 | } 547 | else 548 | { 549 | fprintf( stderr, "%u tests FAILED\n", g_numTestsRun - g_numTestsSucceeded ); 550 | return 1; 551 | } 552 | return 0; 553 | } 554 | -------------------------------------------------------------------------------- /src/TaskScheduler_c.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #include "TaskScheduler_c.h" 20 | #include "TaskScheduler.h" 21 | 22 | using namespace enki; 23 | 24 | #if defined(ENKI_CUSTOM_ALLOC_FILE_AND_LINE) 25 | #define ENKI_FILE_AND_LINE __FILE__, __LINE__ 26 | #else 27 | namespace 28 | { 29 | const char* gc_File = ""; 30 | const uint32_t gc_Line = 0; 31 | } 32 | #define ENKI_FILE_AND_LINE gc_File, gc_Line 33 | #endif 34 | 35 | void* enkiDefaultAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ ) 36 | { 37 | return enki::DefaultAllocFunc( align_, size_, userData_, file_, line_ ); 38 | } 39 | 40 | void enkiDefaultFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ ) 41 | { 42 | return enki::DefaultFreeFunc( ptr_, size_, userData_, file_, line_ ); 43 | } 44 | 45 | struct enkiTaskScheduler : TaskScheduler 46 | { 47 | void enkiSetCustomAllocator( CustomAllocator customAllocator_ ) 48 | { 49 | SetCustomAllocator( customAllocator_ ); 50 | } 51 | }; 52 | 53 | struct enkiCompletable : ICompletable {}; // empty struct which we will use for completables 54 | 55 | struct enkiTaskSet : ITaskSet 56 | { 57 | enkiTaskSet( enkiTaskExecuteRange taskFun_ ) : taskFun(taskFun_), pArgs(NULL) {} 58 | 59 | void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override 60 | { 61 | taskFun( range_.start, range_.end, threadnum_, pArgs ); 62 | } 63 | 64 | enkiTaskExecuteRange taskFun; 65 | void* pArgs = NULL; 66 | }; 67 | 68 | struct enkiPinnedTask : IPinnedTask 69 | { 70 | enkiPinnedTask( enkiPinnedTaskExecute taskFun_, uint32_t threadNum_ ) 71 | : IPinnedTask( threadNum_ ), taskFun(taskFun_), pArgs(NULL) {} 72 | 73 | void Execute() override 74 | { 75 | taskFun( pArgs ); 76 | } 77 | 78 | enkiPinnedTaskExecute taskFun; 79 | void* pArgs = NULL; 80 | }; 81 | 82 | struct enkiCompletionAction : ICompletable 83 | { 84 | void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) override 85 | { 86 | if( completionFunctionPreComplete ) 87 | { 88 | completionFunctionPreComplete( pArgsPreComplete, threadNum_ ); 89 | } 90 | 91 | // make temporaries for post completion as this task could get deleted after OnDependenciesComplete 92 | enkiCompletionFunction tempCompletionFunctionPostComplete = completionFunctionPostComplete; 93 | void* ptempArgsPostComplete = pArgsPostComplete; 94 | 95 | ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ ); 96 | 97 | if( tempCompletionFunctionPostComplete ) 98 | { 99 | tempCompletionFunctionPostComplete( ptempArgsPostComplete, threadNum_ ); 100 | } 101 | } 102 | 103 | enkiCompletionFunction completionFunctionPreComplete; 104 | enkiCompletionFunction completionFunctionPostComplete; 105 | Dependency dependency; 106 | void* pArgsPreComplete = NULL; 107 | void* pArgsPostComplete = NULL; 108 | }; 109 | 110 | struct enkiDependency : Dependency {}; // empty struct which we will use for dependencies 111 | 112 | enkiTaskScheduler* enkiNewTaskScheduler() 113 | { 114 | CustomAllocator defaultAllocator; 115 | enkiTaskScheduler* pETS = (enkiTaskScheduler*)defaultAllocator.alloc( 116 | alignof(enkiTaskScheduler), sizeof(enkiTaskScheduler), defaultAllocator.userData, ENKI_FILE_AND_LINE ); 117 | new(pETS) enkiTaskScheduler; 118 | return pETS; 119 | } 120 | 121 | enkiTaskScheduler* enkiNewTaskSchedulerWithCustomAllocator( struct enkiCustomAllocator customAllocator_ ) 122 | { 123 | enkiTaskScheduler* pETS = (enkiTaskScheduler*)customAllocator_.alloc( 124 | alignof(enkiTaskScheduler), sizeof(enkiTaskScheduler), customAllocator_.userData, ENKI_FILE_AND_LINE ); 125 | 126 | CustomAllocator customAllocatorCpp; 127 | customAllocatorCpp.alloc = customAllocator_.alloc; 128 | customAllocatorCpp.free = customAllocator_.free; 129 | customAllocatorCpp.userData = customAllocator_.userData; 130 | 131 | new(pETS) enkiTaskScheduler; 132 | pETS->enkiSetCustomAllocator( customAllocatorCpp ); 133 | return pETS; 134 | } 135 | 136 | struct enkiTaskSchedulerConfig enkiGetTaskSchedulerConfig( enkiTaskScheduler* pETS_ ) 137 | { 138 | TaskSchedulerConfig config = pETS_->GetConfig(); 139 | enkiTaskSchedulerConfig configC; 140 | configC.numExternalTaskThreads = config.numExternalTaskThreads; 141 | configC.numTaskThreadsToCreate = config.numTaskThreadsToCreate; 142 | configC.profilerCallbacks.threadStart = config.profilerCallbacks.threadStart; 143 | configC.profilerCallbacks.threadStop = config.profilerCallbacks.threadStop; 144 | configC.profilerCallbacks.waitForNewTaskSuspendStart = config.profilerCallbacks.waitForNewTaskSuspendStart; 145 | configC.profilerCallbacks.waitForNewTaskSuspendStop = config.profilerCallbacks.waitForNewTaskSuspendStop; 146 | configC.profilerCallbacks.waitForTaskCompleteStart = config.profilerCallbacks.waitForTaskCompleteStart; 147 | configC.profilerCallbacks.waitForTaskCompleteStop = config.profilerCallbacks.waitForTaskCompleteStop; 148 | configC.profilerCallbacks.waitForTaskCompleteSuspendStart = config.profilerCallbacks.waitForTaskCompleteSuspendStart; 149 | configC.profilerCallbacks.waitForTaskCompleteSuspendStop = config.profilerCallbacks.waitForTaskCompleteSuspendStop; 150 | configC.customAllocator.alloc = config.customAllocator.alloc; 151 | configC.customAllocator.free = config.customAllocator.free; 152 | configC.customAllocator.userData = config.customAllocator.userData; 153 | return configC; 154 | } 155 | 156 | int enkiGetIsRunning( enkiTaskScheduler* pETS_ ) 157 | { 158 | return (int)(!pETS_->GetIsShutdownRequested()); 159 | } 160 | 161 | int enkiGetIsShutdownRequested(enkiTaskScheduler* pETS_) 162 | { 163 | return (int)pETS_->GetIsShutdownRequested(); 164 | } 165 | 166 | int enkiGetIsWaitforAllCalled( enkiTaskScheduler* pETS_ ) 167 | { 168 | return (int)pETS_->GetIsWaitforAllCalled(); 169 | } 170 | 171 | void enkiInitTaskScheduler( enkiTaskScheduler* pETS_ ) 172 | { 173 | pETS_->Initialize(); 174 | } 175 | 176 | void enkiInitTaskSchedulerNumThreads( enkiTaskScheduler* pETS_, uint32_t numThreads_ ) 177 | { 178 | pETS_->Initialize( numThreads_ ); 179 | } 180 | 181 | void enkiInitTaskSchedulerWithConfig( enkiTaskScheduler* pETS_, struct enkiTaskSchedulerConfig config_ ) 182 | { 183 | TaskSchedulerConfig config; 184 | config.numExternalTaskThreads = config_.numExternalTaskThreads; 185 | config.numTaskThreadsToCreate = config_.numTaskThreadsToCreate; 186 | config.profilerCallbacks.threadStart = config_.profilerCallbacks.threadStart; 187 | config.profilerCallbacks.threadStop = config_.profilerCallbacks.threadStop; 188 | config.profilerCallbacks.waitForNewTaskSuspendStart = config_.profilerCallbacks.waitForNewTaskSuspendStart; 189 | config.profilerCallbacks.waitForNewTaskSuspendStop = config_.profilerCallbacks.waitForNewTaskSuspendStop; 190 | config.profilerCallbacks.waitForTaskCompleteStart = config_.profilerCallbacks.waitForTaskCompleteStart; 191 | config.profilerCallbacks.waitForTaskCompleteStop = config_.profilerCallbacks.waitForTaskCompleteStop; 192 | config.profilerCallbacks.waitForTaskCompleteSuspendStart = config_.profilerCallbacks.waitForTaskCompleteSuspendStart; 193 | config.profilerCallbacks.waitForTaskCompleteSuspendStop = config_.profilerCallbacks.waitForTaskCompleteSuspendStop; 194 | config.customAllocator.alloc = config_.customAllocator.alloc; 195 | config.customAllocator.free = config_.customAllocator.free; 196 | config.customAllocator.userData = config_.customAllocator.userData; 197 | pETS_->Initialize( config ); 198 | } 199 | 200 | void enkiWaitforAllAndShutdown( enkiTaskScheduler* pETS_ ) 201 | { 202 | pETS_->WaitforAllAndShutdown(); 203 | } 204 | 205 | void enkiDeleteTaskScheduler( enkiTaskScheduler* pETS_ ) 206 | { 207 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 208 | pETS_->~enkiTaskScheduler(); 209 | customAllocator.free( pETS_, sizeof(enkiTaskScheduler), customAllocator.userData, ENKI_FILE_AND_LINE ); 210 | } 211 | 212 | ENKITS_API uint32_t enkiGetNumFirstExternalTaskThread() 213 | { 214 | return enkiTaskScheduler::GetNumFirstExternalTaskThread(); 215 | } 216 | 217 | enkiTaskSet* enkiCreateTaskSet( enkiTaskScheduler* pETS_, enkiTaskExecuteRange taskFunc_ ) 218 | { 219 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 220 | enkiTaskSet* pTask = (enkiTaskSet*)customAllocator.alloc( 221 | alignof(enkiTaskSet), sizeof(enkiTaskSet), customAllocator.userData, ENKI_FILE_AND_LINE ); 222 | new(pTask) enkiTaskSet( taskFunc_ ); 223 | 224 | return pTask; 225 | } 226 | 227 | void enkiDeleteTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ) 228 | { 229 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 230 | 231 | pTaskSet_->~enkiTaskSet(); 232 | customAllocator.free( pTaskSet_, sizeof(enkiTaskSet), customAllocator.userData, ENKI_FILE_AND_LINE ); 233 | } 234 | 235 | enkiParamsTaskSet enkiGetParamsTaskSet( enkiTaskSet* pTaskSet_ ) 236 | { 237 | enkiParamsTaskSet paramsTaskSet; 238 | paramsTaskSet.pArgs = pTaskSet_->pArgs; 239 | paramsTaskSet.setSize = pTaskSet_->m_SetSize; 240 | paramsTaskSet.minRange = pTaskSet_->m_MinRange; 241 | paramsTaskSet.priority = pTaskSet_->m_Priority; 242 | return paramsTaskSet; 243 | } 244 | 245 | void enkiSetParamsTaskSet( enkiTaskSet* pTaskSet_, enkiParamsTaskSet params_ ) 246 | { 247 | ENKI_ASSERT( params_.priority < ENKITS_TASK_PRIORITIES_NUM ); 248 | pTaskSet_->pArgs = params_.pArgs; 249 | pTaskSet_->m_SetSize = params_.setSize; 250 | pTaskSet_->m_MinRange = params_.minRange; 251 | pTaskSet_->m_Priority = TaskPriority( params_.priority ); 252 | } 253 | 254 | void enkiSetPriorityTaskSet( enkiTaskSet* pTaskSet_, int priority_ ) 255 | { 256 | ENKI_ASSERT( priority_ < ENKITS_TASK_PRIORITIES_NUM ); 257 | pTaskSet_->m_Priority = TaskPriority( priority_ ); 258 | } 259 | 260 | void enkiSetArgsTaskSet( enkiTaskSet* pTaskSet_, void* pArgs_ ) 261 | { 262 | pTaskSet_->pArgs = pArgs_; 263 | } 264 | 265 | void enkiSetSetSizeTaskSet( enkiTaskSet* pTaskSet_, uint32_t setSize_ ) 266 | { 267 | pTaskSet_->m_SetSize = setSize_; 268 | } 269 | 270 | void enkiSetMinRangeTaskSet( enkiTaskSet* pTaskSet_, uint32_t minRange_ ) 271 | { 272 | pTaskSet_->m_MinRange = minRange_; 273 | } 274 | 275 | void enkiAddTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ) 276 | { 277 | ENKI_ASSERT( pTaskSet_ ); 278 | ENKI_ASSERT( pTaskSet_->taskFun ); 279 | 280 | pETS_->AddTaskSetToPipe( pTaskSet_ ); 281 | } 282 | 283 | void enkiAddTaskSetArgs( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, void* pArgs_, uint32_t setSize_ ) 284 | { 285 | ENKI_ASSERT( pTaskSet_ ); 286 | ENKI_ASSERT( pTaskSet_->taskFun ); 287 | 288 | pTaskSet_->m_SetSize = setSize_; 289 | pTaskSet_->pArgs = pArgs_; 290 | pETS_->AddTaskSetToPipe( pTaskSet_ ); 291 | } 292 | 293 | void enkiAddTaskSetMinRange(enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, void* pArgs_, uint32_t setSize_, uint32_t minRange_) 294 | { 295 | ENKI_ASSERT( pTaskSet_ ); 296 | ENKI_ASSERT( pTaskSet_->taskFun ); 297 | 298 | pTaskSet_->m_SetSize = setSize_; 299 | pTaskSet_->m_MinRange = minRange_; 300 | pTaskSet_->pArgs = pArgs_; 301 | pETS_->AddTaskSetToPipe( pTaskSet_ ); 302 | } 303 | 304 | int enkiIsTaskSetComplete( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ) 305 | { 306 | (void)pETS_; 307 | ENKI_ASSERT( pTaskSet_ ); 308 | return ( pTaskSet_->GetIsComplete() ) ? 1 : 0; 309 | } 310 | 311 | enkiPinnedTask* enkiCreatePinnedTask(enkiTaskScheduler* pETS_, enkiPinnedTaskExecute taskFunc_, uint32_t threadNum_) 312 | { 313 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 314 | enkiPinnedTask* pTask = (enkiPinnedTask*)customAllocator.alloc( 315 | alignof(enkiPinnedTask), sizeof(enkiPinnedTask), customAllocator.userData, ENKI_FILE_AND_LINE ); 316 | new(pTask) enkiPinnedTask( taskFunc_, threadNum_ ); 317 | return pTask; 318 | } 319 | 320 | void enkiDeletePinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pPinnedTask_ ) 321 | { 322 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 323 | 324 | pPinnedTask_->~enkiPinnedTask(); 325 | customAllocator.free( pPinnedTask_, sizeof(enkiPinnedTask), customAllocator.userData, ENKI_FILE_AND_LINE ); 326 | } 327 | 328 | enkiParamsPinnedTask enkiGetParamsPinnedTask( enkiPinnedTask* pTask_ ) 329 | { 330 | enkiParamsPinnedTask paramsPinnedTask; 331 | paramsPinnedTask.pArgs = pTask_->pArgs; 332 | paramsPinnedTask.priority = pTask_->m_Priority; 333 | return paramsPinnedTask; 334 | } 335 | 336 | void enkiSetParamsPinnedTask( enkiPinnedTask* pTask_, enkiParamsPinnedTask params_ ) 337 | { 338 | ENKI_ASSERT( params_.priority < ENKITS_TASK_PRIORITIES_NUM ); 339 | pTask_->pArgs = params_.pArgs; 340 | pTask_->m_Priority = TaskPriority( params_.priority ); 341 | } 342 | 343 | void enkiSetPriorityPinnedTask( enkiPinnedTask* pTask_, int priority_ ) 344 | { 345 | ENKI_ASSERT( priority_ < ENKITS_TASK_PRIORITIES_NUM ); 346 | pTask_->m_Priority = TaskPriority( priority_ ); 347 | } 348 | 349 | void enkiSetArgsPinnedTask( enkiPinnedTask* pTask_, void* pArgs_ ) 350 | { 351 | pTask_->pArgs = pArgs_; 352 | } 353 | 354 | void enkiAddPinnedTask(enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_) 355 | { 356 | ENKI_ASSERT( pTask_ ); 357 | pETS_->AddPinnedTask( pTask_ ); 358 | } 359 | 360 | void enkiAddPinnedTaskArgs(enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_, void* pArgs_) 361 | { 362 | ENKI_ASSERT( pTask_ ); 363 | pTask_->pArgs = pArgs_; 364 | pETS_->AddPinnedTask( pTask_ ); 365 | } 366 | 367 | void enkiRunPinnedTasks(enkiTaskScheduler* pETS_) 368 | { 369 | pETS_->RunPinnedTasks(); 370 | } 371 | 372 | int enkiIsPinnedTaskComplete(enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_) 373 | { 374 | ENKI_ASSERT( pTask_ ); 375 | return ( pTask_->GetIsComplete() ) ? 1 : 0; 376 | } 377 | 378 | void enkiWaitForTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ) 379 | { 380 | pETS_->WaitforTask( pTaskSet_ ); 381 | } 382 | 383 | void enkiWaitForTaskSetPriority( enkiTaskScheduler * pETS_, enkiTaskSet * pTaskSet_, int maxPriority_ ) 384 | { 385 | pETS_->WaitforTask( pTaskSet_, TaskPriority( maxPriority_ ) ); 386 | } 387 | 388 | void enkiWaitForPinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ ) 389 | { 390 | pETS_->WaitforTask( pTask_ ); 391 | } 392 | 393 | void enkiWaitForPinnedTaskPriority( enkiTaskScheduler * pETS_, enkiPinnedTask * pTask_, int maxPriority_ ) 394 | { 395 | pETS_->WaitforTask( pTask_, TaskPriority( maxPriority_ ) ); 396 | } 397 | 398 | void enkiWaitForNewPinnedTasks( enkiTaskScheduler* pETS_ ) 399 | { 400 | pETS_->WaitForNewPinnedTasks(); 401 | } 402 | 403 | 404 | void enkiWaitForAll( enkiTaskScheduler* pETS_ ) 405 | { 406 | pETS_->WaitforAll(); 407 | } 408 | 409 | uint32_t enkiGetNumTaskThreads( enkiTaskScheduler* pETS_ ) 410 | { 411 | return pETS_->GetNumTaskThreads(); 412 | } 413 | 414 | uint32_t enkiGetThreadNum( enkiTaskScheduler* pETS_ ) 415 | { 416 | return pETS_->GetThreadNum(); 417 | } 418 | 419 | int enkiRegisterExternalTaskThread( enkiTaskScheduler* pETS_) 420 | { 421 | return (int)pETS_->RegisterExternalTaskThread(); 422 | } 423 | 424 | int enkiRegisterExternalTaskThreadNum( enkiTaskScheduler* pETS_, uint32_t threadNumToRegister_ ) 425 | { 426 | return (int)pETS_->RegisterExternalTaskThread( threadNumToRegister_ ) ; 427 | } 428 | 429 | void enkiDeRegisterExternalTaskThread( enkiTaskScheduler* pETS_) 430 | { 431 | return pETS_->DeRegisterExternalTaskThread(); 432 | } 433 | 434 | uint32_t enkiGetNumRegisteredExternalTaskThreads( enkiTaskScheduler* pETS_) 435 | { 436 | return pETS_->GetNumRegisteredExternalTaskThreads(); 437 | } 438 | 439 | enkiCompletable* enkiGetCompletableFromTaskSet( enkiTaskSet* pTaskSet_ ) 440 | { 441 | return reinterpret_cast( static_cast( pTaskSet_ ) ); 442 | } 443 | 444 | enkiCompletable* enkiGetCompletableFromPinnedTask( enkiPinnedTask* pPinnedTask_ ) 445 | { 446 | return reinterpret_cast( static_cast( pPinnedTask_ ) ); 447 | } 448 | 449 | enkiCompletable* enkiGetCompletableFromCompletionAction( enkiCompletionAction* pCompletionAction_ ) 450 | { 451 | return reinterpret_cast( static_cast( pCompletionAction_ ) ); 452 | } 453 | 454 | enkiCompletable* enkiCreateCompletable( enkiTaskScheduler* pETS_ ) 455 | { 456 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 457 | enkiCompletable* pTask = (enkiCompletable*)customAllocator.alloc( 458 | alignof(enkiCompletable), sizeof(enkiCompletable), customAllocator.userData, ENKI_FILE_AND_LINE ); 459 | new(pTask) enkiCompletable(); 460 | return pTask; 461 | } 462 | 463 | void enkiDeleteCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pCompletable_ ) 464 | { 465 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 466 | 467 | pCompletable_->~enkiCompletable(); 468 | customAllocator.free( pCompletable_, sizeof(enkiCompletable), customAllocator.userData, ENKI_FILE_AND_LINE ); 469 | } 470 | 471 | void enkiWaitForCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pTask_ ) 472 | { 473 | return pETS_->WaitforTask( reinterpret_cast( pTask_ ) ); 474 | } 475 | 476 | void enkiWaitForCompletablePriority( enkiTaskScheduler* pETS_, enkiCompletable* pTask_, int maxPriority_ ) 477 | { 478 | return pETS_->WaitforTask( reinterpret_cast( pTask_ ), TaskPriority( maxPriority_ ) ); 479 | } 480 | 481 | enkiDependency* enkiCreateDependency( enkiTaskScheduler* pETS_ ) 482 | { 483 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 484 | enkiDependency* pDep = (enkiDependency*)customAllocator.alloc( 485 | alignof(enkiDependency), sizeof(enkiDependency), customAllocator.userData, ENKI_FILE_AND_LINE ); 486 | new(pDep) enkiDependency(); 487 | return pDep; 488 | } 489 | 490 | void enkiDeleteDependency( enkiTaskScheduler* pETS_, enkiDependency* pDependency_ ) 491 | { 492 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 493 | 494 | pDependency_->~enkiDependency(); 495 | customAllocator.free( pDependency_, sizeof(enkiDependency), customAllocator.userData, ENKI_FILE_AND_LINE ); 496 | } 497 | 498 | void enkiSetDependency( enkiDependency* pDependency_, enkiCompletable* pDependencyTask_, enkiCompletable* pTaskToRunOnCompletion_ ) 499 | { 500 | pTaskToRunOnCompletion_->SetDependency( *pDependency_, pDependencyTask_ ); 501 | } 502 | 503 | enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunctionPreComplete_, enkiCompletionFunction completionFunctionPostComplete_ ) 504 | { 505 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 506 | enkiCompletionAction* pCA = (enkiCompletionAction*)customAllocator.alloc( 507 | alignof(enkiCompletionAction), sizeof(enkiCompletionAction), customAllocator.userData, ENKI_FILE_AND_LINE ); 508 | new(pCA) enkiCompletionAction(); 509 | pCA->completionFunctionPreComplete = completionFunctionPreComplete_; 510 | pCA->completionFunctionPostComplete = completionFunctionPostComplete_; 511 | return pCA; 512 | } 513 | 514 | void enkiDeleteCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionAction* pCompletionAction_ ) 515 | { 516 | CustomAllocator customAllocator = pETS_->GetConfig().customAllocator; 517 | 518 | pCompletionAction_->~enkiCompletionAction(); 519 | customAllocator.free( pCompletionAction_, sizeof(enkiCompletionAction), customAllocator.userData, ENKI_FILE_AND_LINE ); 520 | } 521 | 522 | enkiParamsCompletionAction enkiGetParamsCompletionAction( enkiCompletionAction* pCompletionAction_ ) 523 | { 524 | enkiParamsCompletionAction params; 525 | params.pArgsPreComplete = pCompletionAction_->pArgsPreComplete; 526 | params.pArgsPostComplete = pCompletionAction_->pArgsPostComplete; 527 | params.pDependency = reinterpret_cast( 528 | pCompletionAction_->dependency.GetDependencyTask() ); 529 | return params; 530 | } 531 | 532 | void enkiSetParamsCompletionAction( enkiCompletionAction* pCompletionAction_, enkiParamsCompletionAction params_ ) 533 | { 534 | pCompletionAction_->pArgsPreComplete = params_.pArgsPreComplete; 535 | pCompletionAction_->pArgsPostComplete = params_.pArgsPostComplete; 536 | pCompletionAction_->SetDependency( pCompletionAction_->dependency, params_.pDependency ); 537 | } 538 | -------------------------------------------------------------------------------- /src/TaskScheduler_c.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Doug Binks 2 | // 3 | // This software is provided 'as-is', without any express or implied 4 | // warranty. In no event will the authors be held liable for any damages 5 | // arising from the use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software 13 | // in a product, an acknowledgement in the product documentation would be 14 | // appreciated but is not required. 15 | // 2. Altered source versions must be plainly marked as such, and must not be 16 | // misrepresented as being the original software. 17 | // 3. This notice may not be removed or altered from any source distribution. 18 | 19 | #pragma once 20 | 21 | #if defined(_WIN32) && defined(ENKITS_BUILD_DLL) 22 | // Building enkiTS as a DLL 23 | #define ENKITS_API __declspec(dllexport) 24 | #elif defined(_WIN32) && defined(ENKITS_DLL) 25 | // Using enkiTS as a DLL 26 | #define ENKITS_API __declspec(dllimport) 27 | #elif defined(__GNUC__) && defined(ENKITS_BUILD_DLL) 28 | // Building enkiTS as a shared library 29 | #define ENKITS_API __attribute__((visibility("default"))) 30 | #else 31 | #define ENKITS_API 32 | #endif 33 | 34 | // Define ENKI_CUSTOM_ALLOC_FILE_AND_LINE (at project level) to get file and line report in custom allocators, 35 | // this is default in Debug - to turn off define ENKI_CUSTOM_ALLOC_NO_FILE_AND_LINE 36 | #ifndef ENKI_CUSTOM_ALLOC_FILE_AND_LINE 37 | #if defined(_DEBUG ) && !defined(ENKI_CUSTOM_ALLOC_NO_FILE_AND_LINE) 38 | #define ENKI_CUSTOM_ALLOC_FILE_AND_LINE 39 | #endif 40 | #endif 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | #include 47 | #include 48 | 49 | typedef struct enkiTaskScheduler enkiTaskScheduler; 50 | typedef struct enkiTaskSet enkiTaskSet; 51 | typedef struct enkiPinnedTask enkiPinnedTask; 52 | typedef struct enkiCompletable enkiCompletable; 53 | typedef struct enkiDependency enkiDependency; 54 | typedef struct enkiCompletionAction enkiCompletionAction; 55 | 56 | static const uint32_t ENKI_NO_THREAD_NUM = 0xFFFFFFFF; 57 | 58 | typedef void (* enkiTaskExecuteRange)( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ); 59 | typedef void (* enkiPinnedTaskExecute)( void* pArgs_ ); 60 | typedef void (* enkiCompletionFunction)( void* pArgs_, uint32_t threadNum_ ); 61 | 62 | // TaskScheduler implements several callbacks intended for profilers 63 | typedef void (*enkiProfilerCallbackFunc)( uint32_t threadnum_ ); 64 | struct enkiProfilerCallbacks 65 | { 66 | enkiProfilerCallbackFunc threadStart; 67 | enkiProfilerCallbackFunc threadStop; 68 | enkiProfilerCallbackFunc waitForNewTaskSuspendStart; // thread suspended waiting for new tasks 69 | enkiProfilerCallbackFunc waitForNewTaskSuspendStop; // thread unsuspended 70 | enkiProfilerCallbackFunc waitForTaskCompleteStart; // thread waiting for task completion 71 | enkiProfilerCallbackFunc waitForTaskCompleteStop; // thread stopped waiting 72 | enkiProfilerCallbackFunc waitForTaskCompleteSuspendStart; // thread suspended waiting task completion 73 | enkiProfilerCallbackFunc waitForTaskCompleteSuspendStop; // thread unsuspended 74 | }; 75 | 76 | // Custom allocator, set in enkiTaskSchedulerConfig. Also see ENKI_CUSTOM_ALLOC_FILE_AND_LINE for file_ and line_ 77 | typedef void* (*enkiAllocFunc)( size_t align_, size_t size_, void* userData_, const char* file_, int line_ ); 78 | typedef void (*enkiFreeFunc)( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ ); 79 | ENKITS_API void* enkiDefaultAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ ); 80 | ENKITS_API void enkiDefaultFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ ); 81 | struct enkiCustomAllocator 82 | { 83 | enkiAllocFunc alloc; 84 | enkiFreeFunc free; 85 | void* userData; 86 | }; 87 | 88 | struct enkiParamsTaskSet 89 | { 90 | void* pArgs; 91 | uint32_t setSize; 92 | uint32_t minRange; 93 | int priority; 94 | }; 95 | 96 | struct enkiParamsPinnedTask 97 | { 98 | void* pArgs; 99 | int priority; 100 | }; 101 | 102 | struct enkiParamsCompletionAction 103 | { 104 | void* pArgsPreComplete; 105 | void* pArgsPostComplete; 106 | const enkiCompletable* pDependency; // task which when complete triggers completion function 107 | }; 108 | 109 | // enkiTaskSchedulerConfig - configuration struct for advanced Initialize 110 | // Always use enkiGetTaskSchedulerConfig() to get defaults prior to altering and 111 | // initializing with enkiInitTaskSchedulerWithConfig(). 112 | struct enkiTaskSchedulerConfig 113 | { 114 | // numTaskThreadsToCreate - Number of tasking threads the task scheduler will create. Must be > 0. 115 | // Defaults to GetNumHardwareThreads()-1 threads as thread which calls initialize is thread 0. 116 | uint32_t numTaskThreadsToCreate; 117 | 118 | // numExternalTaskThreads - Advanced use. Number of external threads which need to use TaskScheduler API. 119 | // See TaskScheduler::RegisterExternalTaskThread() for usage. 120 | // Defaults to 0. The thread used to initialize the TaskScheduler can also use the TaskScheduler API. 121 | // Thus there are (numTaskThreadsToCreate + numExternalTaskThreads + 1) able to use the API, with this 122 | // defaulting to the number of hardware threads available to the system. 123 | uint32_t numExternalTaskThreads; 124 | 125 | struct enkiProfilerCallbacks profilerCallbacks; 126 | 127 | struct enkiCustomAllocator customAllocator; 128 | }; 129 | 130 | 131 | /* ---------------------------- Task Scheduler ---------------------------- */ 132 | // Create a new task scheduler 133 | ENKITS_API enkiTaskScheduler* enkiNewTaskScheduler( void ); 134 | 135 | // Create a new task scheduler using a custom allocator 136 | // This will use the custom allocator to allocate the task scheduler struct 137 | // and additionally will set the custom allocator in enkiTaskSchedulerConfig of the task scheduler 138 | ENKITS_API enkiTaskScheduler* enkiNewTaskSchedulerWithCustomAllocator( struct enkiCustomAllocator customAllocator_ ); 139 | 140 | // Get config. Can be called before enkiInitTaskSchedulerWithConfig to get the defaults 141 | ENKITS_API struct enkiTaskSchedulerConfig enkiGetTaskSchedulerConfig( enkiTaskScheduler* pETS_ ); 142 | 143 | // DEPRECATED: use GetIsShutdownRequested() instead of GetIsRunning() in external code 144 | // while( enkiGetIsRunning(pETS) ) {} can be used in tasks which loop, to check if enkiTS has been shutdown. 145 | // If enkiGetIsRunning() returns false should then exit. Not required for finite tasks 146 | ENKITS_API int enkiGetIsRunning( enkiTaskScheduler* pETS_ ); 147 | 148 | // while( !enkiGetIsShutdownRequested() ) {} can be used in tasks which loop, to check if enkiTS has been requested to shutdown. 149 | // If enkiGetIsShutdownRequested() returns true should then exit. Not required for finite tasks 150 | // Safe to use with enkiWaitforAllAndShutdown() where this will be set 151 | // Not safe to use with enkiWaitforAll(), use enkiGetIsWaitforAllCalled() instead. 152 | ENKITS_API int enkiGetIsShutdownRequested( enkiTaskScheduler* pETS_ ); 153 | 154 | // while( !enkiGetIsWaitforAllCalled() ) {} can be used in tasks which loop, to check if enkiWaitforAll() has been called. 155 | // If enkiGetIsWaitforAllCalled() returns false should then exit. Not required for finite tasks 156 | // This is intended to be used with code which calls enkiWaitforAll(). 157 | // This is also set when the task manager is shutting down, so no need to have an additional check for enkiGetIsShutdownRequested() 158 | ENKITS_API int enkiGetIsWaitforAllCalled( enkiTaskScheduler* pETS_ ); 159 | 160 | // Initialize task scheduler - will create GetNumHardwareThreads()-1 threads, which is 161 | // sufficient to fill the system when including the main thread. 162 | // Initialize can be called multiple times - it will wait for completion 163 | // before re-initializing. 164 | ENKITS_API void enkiInitTaskScheduler( enkiTaskScheduler* pETS_ ); 165 | 166 | // Initialize a task scheduler with numThreads_ (must be > 0) 167 | // will create numThreads_-1 threads, as thread 0 is 168 | // the thread on which the initialize was called. 169 | ENKITS_API void enkiInitTaskSchedulerNumThreads( enkiTaskScheduler* pETS_, uint32_t numThreads_ ); 170 | 171 | // Initialize a task scheduler with config, see enkiTaskSchedulerConfig for details 172 | ENKITS_API void enkiInitTaskSchedulerWithConfig( enkiTaskScheduler* pETS_, struct enkiTaskSchedulerConfig config_ ); 173 | 174 | // Waits for all task sets to complete and shutdown threads - not guaranteed to work unless we know we 175 | // are in a situation where tasks aren't being continuously added. 176 | // pETS_ can then be reused. 177 | // This function can be safely called even if enkiInit* has not been called. 178 | ENKITS_API void enkiWaitforAllAndShutdown( enkiTaskScheduler* pETS_ ); 179 | 180 | // Delete a task scheduler. 181 | ENKITS_API void enkiDeleteTaskScheduler( enkiTaskScheduler* pETS_ ); 182 | 183 | // Waits for all task sets to complete - not guaranteed to work unless we know we 184 | // are in a situation where tasks aren't being continuously added. 185 | ENKITS_API void enkiWaitForAll( enkiTaskScheduler* pETS_ ); 186 | 187 | // Returns the number of threads created for running tasks + number of external threads 188 | // plus 1 to account for the thread used to initialize the task scheduler. 189 | // Equivalent to config values: numTaskThreadsToCreate + numExternalTaskThreads + 1. 190 | // It is guaranteed that enkiGetThreadNum() < enkiGetNumTaskThreads() 191 | ENKITS_API uint32_t enkiGetNumTaskThreads( enkiTaskScheduler* pETS_ ); 192 | 193 | // Returns the current task threadNum. 194 | // Will return 0 for thread which initialized the task scheduler, 195 | // and ENKI_NO_THREAD_NUM for all other non-enkiTS threads which have not been registered ( see enkiRegisterExternalTaskThread() ), 196 | // and < enkiGetNumTaskThreads() for all registered and internal enkiTS threads. 197 | // It is guaranteed that enkiGetThreadNum() < enkiGetNumTaskThreads() unless it is ENKI_NO_THREAD_NUM 198 | ENKITS_API uint32_t enkiGetThreadNum( enkiTaskScheduler* pETS_ ); 199 | 200 | // Call on a thread to register the thread to use the TaskScheduling API. 201 | // This is implicitly done for the thread which initializes the TaskScheduler 202 | // Intended for developers who have threads who need to call the TaskScheduler API 203 | // Returns true if successful, false if not. 204 | // Can only have numExternalTaskThreads registered at any one time, which must be set 205 | // at initialization time. 206 | ENKITS_API int enkiRegisterExternalTaskThread( enkiTaskScheduler* pETS_ ); 207 | 208 | // As enkiRegisterExternalTaskThread() but explicitly requests a given thread number. 209 | // threadNumToRegister_ must be >= GetNumFirstExternalTaskThread() 210 | // and < ( GetNumFirstExternalTaskThread() + numExternalTaskThreads ) 211 | ENKITS_API int enkiRegisterExternalTaskThreadNum( enkiTaskScheduler* pETS_, uint32_t threadNumToRegister_ ); 212 | 213 | // Call on a thread on which RegisterExternalTaskThread has been called to deregister that thread. 214 | ENKITS_API void enkiDeRegisterExternalTaskThread( enkiTaskScheduler* pETS_); 215 | 216 | // Get the number of registered external task threads. 217 | ENKITS_API uint32_t enkiGetNumRegisteredExternalTaskThreads( enkiTaskScheduler* pETS_ ); 218 | 219 | // Get the thread number of the first external task thread. This thread 220 | // is not guaranteed to be registered, but threads are registered in order 221 | // from GetNumFirstExternalTaskThread() up to ( GetNumFirstExternalTaskThread() + numExternalTaskThreads ) 222 | // Note that if numExternalTaskThreads == 0 a for loop using this will be valid: 223 | // for( uint32_t externalThreadNum = GetNumFirstExternalTaskThread(); 224 | // externalThreadNum < ( GetNumFirstExternalTaskThread() + numExternalTaskThreads 225 | // ++externalThreadNum ) { // do something with externalThreadNum } 226 | ENKITS_API uint32_t enkiGetNumFirstExternalTaskThread( void ); 227 | 228 | 229 | /* ---------------------------- TaskSets ---------------------------- */ 230 | // Create a task set. 231 | ENKITS_API enkiTaskSet* enkiCreateTaskSet( enkiTaskScheduler* pETS_, enkiTaskExecuteRange taskFunc_ ); 232 | 233 | // Delete a task set. 234 | ENKITS_API void enkiDeleteTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ); 235 | 236 | // Get task parameters via enkiParamsTaskSet 237 | ENKITS_API struct enkiParamsTaskSet enkiGetParamsTaskSet( enkiTaskSet* pTaskSet_ ); 238 | 239 | // Set task parameters via enkiParamsTaskSet 240 | ENKITS_API void enkiSetParamsTaskSet( enkiTaskSet* pTaskSet_, struct enkiParamsTaskSet params_ ); 241 | 242 | // Set task priority ( 0 to ENKITS_TASK_PRIORITIES_NUM-1, where 0 is highest) 243 | ENKITS_API void enkiSetPriorityTaskSet( enkiTaskSet* pTaskSet_, int priority_ ); 244 | 245 | // Set TaskSet args 246 | ENKITS_API void enkiSetArgsTaskSet( enkiTaskSet* pTaskSet_, void* pArgs_ ); 247 | 248 | // Set TaskSet set setSize 249 | ENKITS_API void enkiSetSetSizeTaskSet( enkiTaskSet* pTaskSet_, uint32_t setSize_ ); 250 | 251 | // Set TaskSet set min range 252 | ENKITS_API void enkiSetMinRangeTaskSet( enkiTaskSet* pTaskSet_, uint32_t minRange_ ); 253 | 254 | // Schedule the task, use parameters set with enkiSet*TaskSet 255 | ENKITS_API void enkiAddTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ); 256 | 257 | // Schedule the task 258 | // overwrites args previously set with enkiSetArgsTaskSet 259 | // overwrites setSize previously set with enkiSetSetSizeTaskSet 260 | ENKITS_API void enkiAddTaskSetArgs( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, 261 | void* pArgs_, uint32_t setSize_ ); 262 | 263 | // Schedule the task with a minimum range. 264 | // This should be set to a value which results in computation effort of at least 10k 265 | // clock cycles to minimize task scheduler overhead. 266 | // NOTE: The last partition will be smaller than m_MinRange if m_SetSize is not a multiple 267 | // of m_MinRange. 268 | // Also known as grain size in literature. 269 | ENKITS_API void enkiAddTaskSetMinRange( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, 270 | void* pArgs_, uint32_t setSize_, uint32_t minRange_ ); 271 | // Check if TaskSet is complete. Doesn't wait. Returns 1 if complete, 0 if not. 272 | ENKITS_API int enkiIsTaskSetComplete( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ); 273 | 274 | // Wait for a given task. 275 | // should only be called from thread which created the task scheduler, or within a task 276 | // if called with 0 it will try to run tasks, and return if none available. 277 | // Only wait for child tasks of the current task otherwise a deadlock could occur. 278 | ENKITS_API void enkiWaitForTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ ); 279 | 280 | // enkiWaitForTaskSetPriority as enkiWaitForTaskSet but only runs other tasks with priority <= maxPriority_ 281 | // Only wait for child tasks of the current task otherwise a deadlock could occur. 282 | ENKITS_API void enkiWaitForTaskSetPriority( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, int maxPriority_ ); 283 | 284 | 285 | /* ---------------------------- PinnedTasks ---------------------------- */ 286 | // Create a pinned task. 287 | ENKITS_API enkiPinnedTask* enkiCreatePinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTaskExecute taskFunc_, uint32_t threadNum_ ); 288 | 289 | // Delete a pinned task. 290 | ENKITS_API void enkiDeletePinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pPinnedTask_ ); 291 | 292 | // Get task parameters via enkiParamsTaskSet 293 | ENKITS_API struct enkiParamsPinnedTask enkiGetParamsPinnedTask( enkiPinnedTask* pTask_ ); 294 | 295 | // Set task parameters via enkiParamsTaskSet 296 | ENKITS_API void enkiSetParamsPinnedTask( enkiPinnedTask* pTask_, struct enkiParamsPinnedTask params_ ); 297 | 298 | // Set PinnedTask ( 0 to ENKITS_TASK_PRIORITIES_NUM-1, where 0 is highest) 299 | ENKITS_API void enkiSetPriorityPinnedTask( enkiPinnedTask* pTask_, int priority_ ); 300 | 301 | // Set PinnedTask args 302 | ENKITS_API void enkiSetArgsPinnedTask( enkiPinnedTask* pTask_, void* pArgs_ ); 303 | 304 | // Schedule a pinned task 305 | // Pinned tasks can be added from any thread 306 | ENKITS_API void enkiAddPinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ ); 307 | 308 | // Schedule a pinned task 309 | // Pinned tasks can be added from any thread 310 | // overwrites args previously set with enkiSetArgsPinnedTask 311 | ENKITS_API void enkiAddPinnedTaskArgs( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_, 312 | void* pArgs_ ); 313 | 314 | // This function will run any enkiPinnedTask* for current thread, but not run other 315 | // Main thread should call this or use a wait to ensure its tasks are run. 316 | ENKITS_API void enkiRunPinnedTasks( enkiTaskScheduler * pETS_ ); 317 | 318 | // Check if enkiPinnedTask is complete. Doesn't wait. Returns 1 if complete, 0 if not. 319 | ENKITS_API int enkiIsPinnedTaskComplete( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ ); 320 | 321 | // Wait for a given pinned task. 322 | // should only be called from thread which created the task scheduler, or within a task 323 | // if called with 0 it will try to run tasks, and return if none available. 324 | // Only wait for child tasks of the current task otherwise a deadlock could occur. 325 | ENKITS_API void enkiWaitForPinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ ); 326 | 327 | // enkiWaitForPinnedTaskPriority as enkiWaitForPinnedTask but only runs other tasks with priority <= maxPriority_ 328 | // Only wait for child tasks of the current task otherwise a deadlock could occur. 329 | ENKITS_API void enkiWaitForPinnedTaskPriority( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_, int maxPriority_ ); 330 | 331 | // Waits for the current thread to receive a PinnedTask 332 | // Will not run any tasks - use with RunPinnedTasks() 333 | // Can be used with both ExternalTaskThreads or with an enkiTS tasking thread to create 334 | // a thread which only runs pinned tasks. If enkiTS threads are used can create 335 | // extra enkiTS task threads to handle non blocking computation via normal tasks. 336 | ENKITS_API void enkiWaitForNewPinnedTasks( enkiTaskScheduler* pETS_ ); 337 | 338 | 339 | /* ---------------------------- Completables ---------------------------- */ 340 | // Get a pointer to an enkiCompletable from an enkiTaskSet. 341 | // Do not call enkiDeleteCompletable on the returned pointer. 342 | ENKITS_API enkiCompletable* enkiGetCompletableFromTaskSet( enkiTaskSet* pTaskSet_ ); 343 | 344 | // Get a pointer to an enkiCompletable from an enkiPinnedTask. 345 | // Do not call enkiDeleteCompletable on the returned pointer. 346 | ENKITS_API enkiCompletable* enkiGetCompletableFromPinnedTask( enkiPinnedTask* pPinnedTask_ ); 347 | 348 | // Get a pointer to an enkiCompletable from an enkiPinnedTask. 349 | // Do not call enkiDeleteCompletable on the returned pointer. 350 | ENKITS_API enkiCompletable* enkiGetCompletableFromCompletionAction( enkiCompletionAction* pCompletionAction_ ); 351 | 352 | // Create an enkiCompletable 353 | // Can be used with dependencies to wait for their completion. 354 | // Delete with enkiDeleteCompletable 355 | ENKITS_API enkiCompletable* enkiCreateCompletable( enkiTaskScheduler* pETS_ ); 356 | 357 | // Delete an enkiCompletable created with enkiCreateCompletable 358 | ENKITS_API void enkiDeleteCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pCompletable_ ); 359 | 360 | // Wait for a given completable. 361 | // should only be called from thread which created the task scheduler, or within a task 362 | // if called with 0 it will try to run tasks, and return if none available. 363 | // Only wait for child tasks of the current task otherwise a deadlock could occur. 364 | ENKITS_API void enkiWaitForCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pTask_ ); 365 | 366 | // enkiWaitForCompletablePriority as enkiWaitForCompletable but only runs other tasks with priority <= maxPriority_ 367 | // Only wait for child tasks of the current task otherwise a deadlock could occur. 368 | ENKITS_API void enkiWaitForCompletablePriority( enkiTaskScheduler* pETS_, enkiCompletable* pTask_, int maxPriority_ ); 369 | 370 | 371 | /* ---------------------------- Dependencies ---------------------------- */ 372 | // Create an enkiDependency, used to set dependencies between tasks 373 | // Call enkiDeleteDependency to delete. 374 | ENKITS_API enkiDependency* enkiCreateDependency( enkiTaskScheduler* pETS_ ); 375 | 376 | // Delete an enkiDependency created with enkiCreateDependency. 377 | ENKITS_API void enkiDeleteDependency( enkiTaskScheduler* pETS_, enkiDependency* pDependency_ ); 378 | 379 | // Set a dependency between pDependencyTask_ and pTaskToRunOnCompletion_ 380 | // such that when all dependencies of pTaskToRunOnCompletion_ are completed it will run. 381 | ENKITS_API void enkiSetDependency( 382 | enkiDependency* pDependency_, 383 | enkiCompletable* pDependencyTask_, 384 | enkiCompletable* pTaskToRunOnCompletion_ ); 385 | 386 | /* -------------------------- Completion Actions --------------------------- */ 387 | // Create a CompletionAction. 388 | // completionFunctionPreComplete_ - function called BEFORE the complete action task is 'complete', which means this is prior to dependent tasks being run. 389 | // this function can thus alter any task arguments of the dependencies. 390 | // completionFunctionPostComplete_ - function called AFTER the complete action task is 'complete'. Dependent tasks may have already been started. 391 | // This function can delete the completion action if needed as it will no longer be accessed by other functions. 392 | // It is safe to set either of these to NULL if you do not require that function 393 | ENKITS_API enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunctionPreComplete_, enkiCompletionFunction completionFunctionPostComplete_ ); 394 | 395 | // Delete a CompletionAction. 396 | ENKITS_API void enkiDeleteCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionAction* pCompletionAction_ ); 397 | 398 | // Get task parameters via enkiParamsTaskSet 399 | ENKITS_API struct enkiParamsCompletionAction enkiGetParamsCompletionAction( enkiCompletionAction* pCompletionAction_ ); 400 | 401 | // Set task parameters via enkiParamsTaskSet 402 | ENKITS_API void enkiSetParamsCompletionAction( enkiCompletionAction* pCompletionAction_, struct enkiParamsCompletionAction params_ ); 403 | 404 | 405 | #ifdef __cplusplus 406 | } 407 | #endif 408 | --------------------------------------------------------------------------------