Games Task Scheduler (GTS)
A multi-processor scheduling framework for games engines
6_weakly_dynamic_task_graph_wavefront.h
1 /*******************************************************************************
2 * Copyright 2019 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files(the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions :
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 ******************************************************************************/
22 #pragma once
23 
24 #include <inttypes.h>
25 #include <vector>
26 #include <chrono>
27 
28 #include <gts/micro_scheduler/WorkerPool.h>
29 #include <gts/micro_scheduler/MicroScheduler.h>
30 
31 using namespace gts;
32 
33 namespace gts_examples {
34 
37 class GridSumTask : public Task
38 {
39 public:
40 
41  virtual Task* execute(TaskContext const& ctx) final
42  {
43  sum = predecessorSum[0] + predecessorSum[1];
44 
45  for(int iSucc = 0; iSucc < 2; ++iSucc)
46  {
47  if (pSuccessors[iSucc])
48  {
49  pSuccessors[iSucc]->predecessorSum[iSucc] = sum;
50  if (pSuccessors[iSucc]->removeRef(1) == 1)
51  {
52  ctx.pMicroScheduler->spawnTask(pSuccessors[iSucc]);
53  }
54  }
55  }
56 
57  return nullptr;
58  }
59 
60  GridSumTask* pSuccessors[2];
61  uint64_t predecessorSum[2] = {0, 0};
62  uint64_t sum = 0;
63 };
64 
65 //------------------------------------------------------------------------------
66 void weaklyDynamicTaskGraph_wavefront(int width, int height)
67 {
68  printf ("================\n");
69  printf ("weaklyDynamicTaskGraph_wavefront\n");
70  printf ("================\n");
71 
72 
73  /*
74  The following examples generate a task graph over a 2D grid, were each
75  task computes a 2D prefix sum S(i,j) = S(i-1,j) + S(i,j-1).
76 
77  (0,0) --------> (1,0) --------> ... -> (width-1,0)
78  | | |
79  V V V
80  (0,1) --------> (1,1) --------> ... -> (width-1,1)
81  | | |
82  V V V
83  . . .
84  . . .
85  . . .
86  | | |
87  V V V
88  (0,height-1) -> (1,height-1) -> ... -> (width-1,height-1)
89 
90  This example builds a static representation of the graph.
91  */
92 
93  // Init boilerplate
94  WorkerPool workerPool;
95  bool result = workerPool.initialize();
96  GTS_ASSERT(result);
97  MicroScheduler microScheduler;
98  result = microScheduler.initialize(&workerPool);
99  GTS_ASSERT(result);
100 
101  auto start = std::chrono::high_resolution_clock::now();
102 
103  //
104  // Pre-build the graph topology.
105 
106  std::vector<std::vector<GridSumTask*>> taskGrid(width);
107 
108  for (int ii = width - 1; ii >= 0; --ii)
109  {
110  taskGrid[ii].resize(height);
111 
112  for (int jj = height - 1; jj >= 0; --jj)
113  {
114  GridSumTask* pTask = microScheduler.allocateTask<GridSumTask>();
115 
116  // Add a reference for each incoming edge.
117  pTask->addRef((ii > 0) + (jj > 0), memory_order::relaxed);
118 
119  // Point to each successor so this Task can decrement its reference
120  // once it's execute.
121  pTask->pSuccessors[0] = ii + 1 < width ? taskGrid[ii+1][jj] : nullptr;
122  pTask->pSuccessors[1] = jj + 1 < height ? taskGrid[ii][jj+1] : nullptr;
123 
124  taskGrid[ii][jj] = pTask;
125  }
126  }
127 
128  //
129  // Execute the graph.
130 
131  // Get the first and last tasks.
132  GridSumTask* pRoot = taskGrid[0][0];
133  GridSumTask* pLast = taskGrid[width-1][height-1];
134 
135  // Set the initial value of the sum at the root.
136  pRoot->predecessorSum[0] = 1;
137 
138  // Add a reference to the last Task so that it's not implicitly destroyed.
139  // We want to query the last node for the sum.
140  pLast->addRef(1, memory_order::relaxed);
141 
142  // Spawn the root task and wait for for the graph to decrement pLast's
143  // reference count.
144  pLast->spawnAndWaitForAll(pRoot);
145 
146  // Execute the task manually since we added a ref.
147  pLast->execute(TaskContext());
148 
149  printf("Sum: %" PRIu64 "\n", pLast->sum);
150 
151  // Destroy the task manually since we added a ref.
152  microScheduler.destoryTask(pLast);
153 
154  auto end = std::chrono::high_resolution_clock::now();
155  std::cout << "Time (ms): " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
156 }
157 
158 } // namespace gts_examples
A work-stealing task scheduler. The scheduler is executed by the WorkerPool it is initialized with.
Definition: MicroScheduler.h:81
void spawnTask(Task *pTask, uint32_t priority=0)
Spawns the specified 'pTask' to be executed by the scheduler. Spawned tasks are executed in LIFO orde...
bool initialize(WorkerPool *pWorkerPool)
Initializes the MicroScheduler and attaches it to pWorkPool, where each worker in pWorkPool will exec...
void destoryTask(Task *pTask)
Manually destroy pTask. Undefined if this Task is executing.
GTS_INLINE TTask * allocateTask(TArgs &&... args)
Allocates a new Task object of type TTask.
Definition: MicroScheduler.h:145
A Task payload that embeds TFunc and TArgs into the Task's data. It makes it easy to construct a Task...
Definition: Task.h:120
GTS_INLINE int32_t addRef(int32_t count=1, gts::memory_order order=gts::memory_order::seq_cst)
Definition: Task.inl:84
A collection of running Worker threads that a MicroScheduler can be run on.
Definition: WorkerPool.h:54
bool initialize(uint32_t threadCount=0)
Definition: 6_weakly_dynamic_task_graph_wavefront.h:38
#define GTS_ASSERT(expr)
Causes execution to break when expr is false.
Definition: Assert.h:144
The context associated with the task being executed.
Definition: MicroSchedulerTypes.h:54
MicroScheduler * pMicroScheduler
The MicroScheduler executing the Task.
Definition: MicroSchedulerTypes.h:59