Intel Game Task Scheduler (GTS)
What it is?
GTS is a light-weight, C++, experimental task scheduling framework designed with a "Bring Your Own Engine/Framework" style. It consists of a work-stealing micro-scheduler and a persistent DAG macro-scheduler. It also incorporates parallel containers, threading constructs, and debugging utilities.
Why?
- The need for a simple, light-weight, and engine friendly task scheduler.
- The need for a framework that allows the game development community to experiment with different scheduling algorithms - easily.
- A place to house state-of-the-art algorithms on task scheduling for games from both Intel and the community.
- A place to learn about different task scheduling algorithms and parallel computing constructs.
- Encourage games to become more parallel so they can compute more cool stuff!
How to build
- Download premake 5.0 https://premake.github.io/ and place it in "premake/_scripts_"
- In the "premake" folder, run the "gts.bat" file.
- Move back to the root "gts" directory and there should be an "_build..." folder with the solution in it.
Tutorials/Examples
- Download premake 5.0 https://premake.github.io/ and place it in "premake/_scripts_"
- In the "premake" folder, run the "gts_examples.bat" file.
- Move back to the root "gts" directory and there should be an "_build..." folder with the solution in it.
- Example projects are numbered and should be read through in-order. Please issue anything that needs more explaination.
(*Doxygen and more examples coming soon!)
Features
NOTE: * indicates you can replace the implementation with your own.
Micro-scheduler
- Help-first work-stealing
- Fork-join with nested parallelism
- Supports arbitrary DAGs
- Blocking joins
- Continuation joins
- Scheduler by-passing and task recycling optimizations
- Task affinities. (Force a task to run on a specific thread.)
- Task priorities with starvation resistance
- Scheduler partitioning
- Task execution isolation
Patterns
- Parallel for
- Parallel reduce
- (Parallel sort) - coming soon!
- (Parallel radix sort) - coming soon!
- (Parallel scan) - coming soon!
- Data partitioners
- 1D/2D/3D iteration ranges
Macro-scheduler (*** Work-in-progress ***)
- Persistent, DAG structures
- Extendible for custom scheduler implementations/experimentations
- Allows multiple workloads to be attached to a task for heterogeneous scheduling
Containers
Parallel
- Queue single-producer single-consumer
- Queue single-producer multi-consumer
- Queue multi-producer single-consumer
- Queue multi-producer multi-consumer
- Distributed allocator
- (Hash table) - coming soon!
Serial
- Aligned allocator
- Ring buffer
- Vector*
Platform
- Assert*
- Atomics*
- Threads and synchronization*
- OS memory*
- Machine specific definitions/wrappers*
Analysis
- Analyzer to gather stats for scheduling algorithm
- Concurrent logger for per thread logging (output redirection*)
- Instrumenter*
Known issues
- Code comments may be incomplete, this is a work-in-progress
- Testing is on-going and mostly complete. If you encounter issues please let us know.
- Comments may contain spelling and grammar errors.
- Only works on Windows with VS2015 and VS2017 (MSVC and Clang). Cross platform/compiler code is in the works!
Example
A parallel-for that increments each element in an array.
Convenient version for simple cases:
// Create an array of 0s.
uint32_t const elementCount = 1 << 16;
gts::Vector<char> vec(elementCount, 0);
// Make a parallel-for object for this scheduler. We do this because
// there can be multiple scheduler objects.
ParallelFor parallelFor(taskScheduler);
// Similar to std::for_each.
parallelFor(vec.begin(), vec.end(), [](auto iter) { (*iter)++; });Complex version for times when full control is needed:
size_t const elementCount = 1 << 16;
// Create an array of 0s.
gts::Vector<char> vec(0, elementCount);
// Make a parallel-for object for this scheduler.
ParallelFor parallelFor(taskScheduler);
// The partitioner determines how the data in BlockedRange1d is divided
// up amongst all the worker threads. The AdaptivePartitioner type only
// divides when other worker threads need more work.
auto partitionerType = AdaptivePartitioner();
// Since the partitioner is adaptive, a block size of 1 gives the paritioner
// full control over division.
size_t const blockSize = 1;
parallelFor(
// The 1-D iteration range parallel-for will iterate over.
BlockedRange1d<gts::Vector<char>::iterator>(vec.begin(), vec.end(), blockSize),
// The lambda that parallel-for will map to each block of the range.
[](BlockedRange1d<gts::Vector<char>::iterator>& range, void* pUserData, TaskContext const&)
{
// For each item in the block, increment the value.
for (auto iter = range.begin(); iter != range.end(); ++iter)
{
(*iter)++;
}
},
// The partitioner object.
partitionerType,
// No user data.
nullptr
);References
- https://www.threadingbuildingblocks.org/
- http://supertech.csail.mit.edu/papers/steal.pdf
- https://www.cs.cmu.edu/~guyb/papers/locality2000.pdf
- https://arxiv.org/abs/1806.11128
- https://www.cse.wustl.edu/~kunal/resources/Papers/nabbit.pdf
Contribute
Test Build
- Download premake 5.0 https://premake.github.io/ and place it in "premake/_scripts_".
- Run "git submodules update --init" to pull in googletest.
- In the "premake" folder, run the "gts_unit_test.bat" file.
- Move back to the root "gts" directory and there should be an "_build..." folder with the test solution in it.
Running Test
All tests run as a post-build step. Any failing tests will fail the build.
Licencing
Contributors of new files should add a copyright header at the top of every new source code file with their copyright along with the MIT licensing stub.
Formatting
Please format like the existing code.