HTGS
v2.0
The Hybrid Task Graph Scheduler
|
In this tutorial we will be introducing the basics of the Hybrid Task Graph Scheduler (HTGS) API.
The Source Code can be viewed in the HTGS-Tutorials github repository.
We will be implementing a simple add function to add two numbers and return the result, which introduces the API and how to work with it.
Below we will go into detail on each of the components needed to implement the algorithm x+y=z. Before we implement any code, we must analyze the algorithm and transform it into a dataflow graph. The dataflow graph provides a high level view of the algorithm such as data dependencies, control flow, and compute instances.
Algorithm:
Dataflow graph:
TaskGraph:
We transform the dataflow graph into a TaskGraph. We have two input types for the x+y operation, so we will compose the inputs into a single object to hold both x and y data. The result of the graph is a single value, so another data object is used to hold the ouput. There will be one compute task, which is responsible for the operation x+y and producing the result z.
The algorithm we are implementing adds two numbers and returns a result. One htgs::IData class will be responsible for passing the two numbers to a htgs::ITask and another htgs::IData class will store the output. htgs::IData is defined by inheriting the htgs::IData interface.
When data is passed from htgs::ITask to htgs::ITask (or as input/output of a htgs::TaskGraphConf) it is inserted into a htgs::Connector, which uses a FIFO queue to hold data (transformed into a priority queue using the USE_PRIORITY_QUEUE directive)
If a htgs::ITask expects multiple input values, then htgs::IData can act as a container where each of those input values are stored within a single object.
All data in HTGS is represented as htgs::IData. htgs::IData is an interface and contains only one (optional) virtual function: htgs::IData::compare. The compare function can be used to customize the ordering of data in the htgs::Connector's queue. Priority is enabled only if the USE_PRIORITY_QUEUE directive is defined.
Example altering the order of data to highest value first (USE_PRIORITY_QUEUE directive to enable priority):
To implement the add function of the algorithm, we will only be needing one htgs::ITask. A task is defined by inheriting htgs::ITask.
The htgs::ITask specifies five virtual functions that customize the functionality of a task:
Every htgs::ITask has an input type and and output type. These types are defined by the first and second template parameters, respectively.
The number of threads that are associated with a htgs::ITask and how it interacts with the thread are controlled using one of the four htgs::ITask::ITask constructors. For example using htgs::ITask::ITask(size_t numThreads) will allocate numThreads threads for this htgs::ITask. By doing so, increases the number of threads consuming input data. The htgs::ITask also has additional capabilities that can be used in conjunction with the htgs::ExecutionPipeline, such as getting the pipelineId. These advanced features will be described in a later tutorial.
A htgs::TaskManager is created when adding a htgs::ITask to a htgs::TaskGraphConf. The htgs::TaskManager interacts with the htgs::ITask by sending the htgs::ITask data and producing data when the htgs::ITask::addResult is called. The htgs::TaskManager can be controlled through some of the more advanced constructors from the htgs::ITask. Below is a brief description of what these parameters do.
The interaction between the ITask, htgs::TaskManager, and htgs::TaskManagerThread is shown below:
The htgs::TaskGraphConf is used to connect htgs::ITask's into a graph that is executed concurrently. The premise of the htgs::TaskGraphConf is to provide a separate of concerns between managing memory, dependencies, and computation. A htgs::TaskGraphConf has an input and output type, which are used to produce and consume data to/from the htgs::TaskGraphConf.
These are the primary functions used to add a htgs::ITask to a htgs::TaskGraphConf
2) htgs::TaskGraphConf::addGraphProducerTask
3) htgs::TaskGraphConf::addEdge (Demonstrated in future tutorials)
4) htgs::TaskGraphConf::addRuleEdge (Demonstrated in future tutorials)
5) htgs::TaskGraphConf::addMemoryManagerEdge (Demonstrated in future tutorials)
There are two steps necessary for adding data to a graph and ensuring the graph will finish executing.
When there is no more data to be produced: htgs::TaskGraphConf::finishedProducingData
To process the output of a TaskGraph use the htgs::TaskGraphConf::consumeData. To determine if there is no more data being produced by the TaskGraph, use htgs::TaskGraphConf::isOutputTerminated.
To execute a htgs::TaskGraphConf use the htgs::TaskGraphRuntime. The htgs::TaskGraphRuntime will create and launch threads. If a htgs::ITask has more than one thread specified, then the htgs::TaskGraphRuntime will duplicate the htgs::ITask such that each thread will be responsible for a separate instance of the htgs::ITask.
The htgs::TaskGraphRuntime specifies the following functions:
Calling delete on the runtime will release all memory associated with the htgs::TaskGraphConf, including htgs::ITask allocations.
Sample execution:
If there are complications when running a htgs::TaskGraphConf, then the configuration can be saved as a dot file with the function htgs::TaskGraphConf::writeDotToFile, which can be visually shown with the following command 'dot -Tpng <filename> -o <filename>.png' from Graphviz. Below is an example dot file that is generated and the associated image representation:
Dot file generated using "taskGraph->writeDotToFile("tutorial1.dot")":
And the image generated with GraphViz using "dot -Tpng tutorial1.dot -o tutorial1.png"
If the htgs::TaskGraphConf::writeDotToFile is used after the htgs::TaskGraphRuntime has finished and the PROFILE directive has been defined, then profiling data will be generated within the dot file representation. Various flags can be used to customize the dot file, found in <htgs/types/TaskGraphDotGenFlags.hpp>.
In this tutorial, we looked at the basics of the HTGS API.
In Tutorial2a and Tutorial2b, we will introduce two operations that assist in representing algorithms that contain dependencies with the htgs::Bookkeeper and strict memory limitations using the htgs::MemoryManager.
Additional information: