Games Task Scheduler (GTS)
A multi-processor scheduling framework for games engines
5_partition_init.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 #include "gts/platform/Thread.h"
23 
24 #include "gts/micro_scheduler/WorkerPool.h"
25 #include "gts/micro_scheduler/MicroScheduler.h"
26 
27 using namespace gts;
28 
29 namespace gts_examples {
30 
31 void partitionInit()
32 {
33  printf ("================\n");
34  printf ("partitionInit\n");
35  printf ("================\n");
36 
37  // Imagine a scenario where there are long-running, multi-frame Tasks that need to run
38  // along side a normal update loop. If the long-running Tasks are mixed with
39  // the update-loop, the update-loop Tasks could starve. To handle this quality-of-service
40  // issue, processors can be partitioned into two WorkerPools, one to handle the update-loop
41  // Tasks and one to handle the long-running Tasks. The goal of this configuration is to keep Tasks
42  // isolated. The only time it may be ok to break this isolation is when the long-running Micro-scheduler
43  // is out of Tasks, where it can victimize the update-loop Tasks.
44 
45  // Aside: One could also solve this problem similar to the 3_priority_init.h
46  // example with the update loop running higher priority, however issues
47  // could arise with cache thrashing when the tasks are context switched.
48 
49  // Scheduler for update loop tasks.
50  WorkerPoolDesc mainPoolDesc;
51  WorkerPool mainWorkerPool;
52  MicroScheduler mainMicroScheduler;
53 
54  // Scheduler for multi-frame tasks.
55  WorkerPoolDesc slowPoolDesc;
56  WorkerPool slowWorkerPool;
57  MicroScheduler slowMicroScheduler;
58 
59  // Lets split the CPU so that the first N-1 cores execute update loop tasks
60  // and the last core updates the multi-frame tasks.
61 
62  AffinitySet mainPoolAffinity;
63  AffinitySet slowPoolAffinity;
64 
65  int numMainThreads = 0;
66  int numSlowThreads = 0;
67 
68  // Get the CPU topology.
69  SystemTopology sysTopo;
71 
72  // Lets assume we only have one group for this example.
73  ProcessorGroupInfo const& groupInfo = sysTopo.pGroupInfoArray[0];
74 
75  // Loop through the first N-1 cores and consolidate the affinities.
76  for (size_t iCore = 0; iCore < groupInfo.coreInfoElementCount - 1; ++iCore)
77  {
78  CpuCoreInfo const& coreInfo = groupInfo.pCoreInfoArray[iCore];
79 
80  for (size_t iThead = 0; iThead < coreInfo.hardwareThreadIdCount; ++iThead)
81  {
82  // Consolidate.
83  mainPoolAffinity.set(coreInfo.pHardwareThreadIds[iThead]);
84  ++numMainThreads;
85  }
86  }
87 
88  // Get the affinities of the last core.
89  {
90  CpuCoreInfo const& coreInfo = groupInfo.pCoreInfoArray[groupInfo.coreInfoElementCount - 1];
91 
92  for (size_t iThead = 0; iThead < coreInfo.hardwareThreadIdCount; ++iThead)
93  {
94  // Consolidate.
95  slowPoolAffinity.set(coreInfo.pHardwareThreadIds[iThead]);
96  ++numSlowThreads;
97  }
98  }
99 
100  WorkerPoolDesc workerPoolDesc;
101 
102  //
103  // Build the mainWorkerPool description
104 
105  // The Mater thread IS part of this partition. Affinitize the Master thread here
106  // since the Worker Pool does not own it.
107  AffinitySet masterAffinity;
108  masterAffinity.set(sysTopo.pGroupInfoArray[0].pCoreInfoArray[0].pHardwareThreadIds[0]);
109  gts::ThisThread::setAffinity(0, masterAffinity);
110 
111  for (int iWorker = 0; iWorker < numMainThreads; ++iWorker)
112  {
113  WorkerThreadDesc workerDesc;
114  // Set the affinity.
115  workerDesc.affinity = {0, mainPoolAffinity};
116  // Set the thread's name.
117  sprintf(workerDesc.name, "MainWorker %d", iWorker);
118  // Add the worker desc to the pool desc.
119  mainPoolDesc.workerDescs.push_back(workerDesc);
120  }
121  sprintf(mainPoolDesc.name, "MainWorkerPool");
122  mainWorkerPool.initialize(mainPoolDesc);
123  mainMicroScheduler.initialize(&mainWorkerPool);
124 
125  //
126  // Build the slowWorkerPool description
127 
128  // The Mater thread IS NOT part of this partition. Add filler desc for the Master.
129  slowPoolDesc.workerDescs.push_back(WorkerThreadDesc());
130 
131  for (int iWorker = 0; iWorker < numSlowThreads; ++iWorker)
132  {
133  WorkerThreadDesc workerDesc;
134  // Set the affinity.
135  workerDesc.affinity = {0, slowPoolAffinity};
136  // Set the thread's name.
137  sprintf(workerDesc.name, "SlowWorker %d", iWorker);
138  // Add the worker desc to the pool desc.
139  slowPoolDesc.workerDescs.push_back(workerDesc);
140  }
141  sprintf(slowPoolDesc.name, "SlowWorkerPool");
142  slowWorkerPool.initialize(slowPoolDesc);
143  slowMicroScheduler.initialize(&slowWorkerPool);
144 
145  // Further, let say that the slowMicroScheduler can become idle. Instead of
146  // letting CPU resources go to waste, we can have the slowMicroScheduler
147  // help out the mainMicroScheduler. To do this the mainMicroScheduler becomes
148  // a victim of the slowTaskScehduler.
149 
150  slowMicroScheduler.addExternalVictim(&mainMicroScheduler);
151 
152  // Now, once all tasks local to slowMicroScheduler are complete,
153  // slowMicroScheduler will try to steal work from mainMicroScheduler.
154 }
155 
156 } // namespace gts_examples
A work-stealing task scheduler. The scheduler is executed by the WorkerPool it is initialized with.
Definition: MicroScheduler.h:81
bool initialize(WorkerPool *pWorkerPool)
Initializes the MicroScheduler and attaches it to pWorkPool, where each worker in pWorkPool will exec...
void addExternalVictim(MicroScheduler *pScheduler)
Adds 'pScheduler' as an external victim. All Schedules in this MicroScheduler will be able to steal f...
A collection of running Worker threads that a MicroScheduler can be run on.
Definition: WorkerPool.h:54
bool initialize(uint32_t threadCount=0)
uint32_t * pHardwareThreadIds
The ID for each hardware thread in the core.
Definition: Thread.h:156
CpuCoreInfo * pCoreInfoArray
A array of descriptions for each processor core in the group.
Definition: Thread.h:211
static GTS_INLINE bool setAffinity(size_t groupId, AffinitySet const &affinity)
Sets the processor affinity for the thread and returns the previous affinity.
Definition: Thread.h:497
ProcessorGroupInfo * pGroupInfoArray
A array of descriptions for each processor group.
Definition: Thread.h:238
static GTS_INLINE void getSystemTopology(SystemTopology &out)
Gets the topology of all the processors in the system.
Definition: Thread.h:441
size_t hardwareThreadIdCount
The number of elements in pHardwareThreadIds.
Definition: Thread.h:158
size_t coreInfoElementCount
The number of elements in pCoreInfoArray.
Definition: Thread.h:213
A description of a CPU core.
Definition: Thread.h:149
A description of a processor group.
Definition: Thread.h:202
A description of the system's processor topology.
Definition: Thread.h:231
The description of a WorkerPool.
Definition: MicroSchedulerTypes.h:172
gts::Vector< WorkerThreadDesc > workerDescs
The description of each worker thread.
Definition: MicroSchedulerTypes.h:184
char name[DESC_NAME_SIZE]
A name to help with debugging.
Definition: MicroSchedulerTypes.h:224
The description of a worker Thread.
Definition: MicroSchedulerTypes.h:87
char name[DESC_NAME_SIZE]
The thread's name.
Definition: MicroSchedulerTypes.h:132
WorkerThreadDesc::GroupAndAffinity affinity
Specifies the thread affinity for each processor group. See gts::Thread::getSystemTopology to identif...
Definition: MicroSchedulerTypes.h:114