1 // The MIT License (MIT)
2 //
3 // Copyright (c) 2015 Sergey Makeev, Vadim Slyusarev
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
22
23 #include "Tests.h"
24 #include <UnitTest++.h>
25 #include <MTScheduler.h>
26
27 /*
28
29 Check that every worker thread executed tasks in specified priority. HIGH/NORMAL/LOW
30
31 */
SUITE(PriorityTests)32 SUITE(PriorityTests)
33 {
34 static const uint32 TASK_COUNT = 512;
35
36 MT::Atomic32<int32> switchCountToNormal;
37 MT::Atomic32<int32> switchCountToLow;
38
39
40 struct ThreadState
41 {
42 uint32 taskPrio;
43 uint32 highProcessed;
44 uint32 normalProcessed;
45 uint32 lowProcessed;
46 byte cacheLine[64];
47
48 ThreadState()
49 {
50 Reset();
51 }
52
53 void Reset()
54 {
55 taskPrio = 0;
56 highProcessed = 0;
57 normalProcessed = 0;
58 lowProcessed = 0;
59 }
60 };
61
62 ThreadState workerState[64];
63
64 struct TaskHigh
65 {
66 MT_DECLARE_TASK(TaskHigh, MT::StackRequirements::STANDARD, MT::TaskPriority::HIGH, MT::Color::Blue);
67
68 uint32 id;
69
70 TaskHigh(uint32 _id)
71 : id(_id)
72 {
73 }
74
75 void Do(MT::FiberContext& ctx)
76 {
77 uint32 workerIndex = ctx.GetThreadContext()->workerIndex;
78 MT_ASSERT(workerIndex < MT_ARRAY_SIZE(workerState), "Invalid worker index");
79 ThreadState& state = workerState[workerIndex];
80
81 CHECK_EQUAL((uint32)0, state.normalProcessed);
82 CHECK_EQUAL((uint32)0, state.lowProcessed);
83
84 state.highProcessed++;
85
86 // Check we in right state (executing HIGH priority tasks)
87 CHECK_EQUAL((uint32)0, state.taskPrio);
88
89 }
90 };
91
92
93 struct TaskNormal
94 {
95 MT_DECLARE_TASK(TaskNormal, MT::StackRequirements::STANDARD, MT::TaskPriority::NORMAL, MT::Color::Blue);
96
97 uint32 id;
98
99 TaskNormal(uint32 _id)
100 : id(_id)
101 {
102 }
103
104 void Do(MT::FiberContext& ctx)
105 {
106 uint32 workerIndex = ctx.GetThreadContext()->workerIndex;
107 MT_ASSERT(workerIndex < MT_ARRAY_SIZE(workerState), "Invalid worker index");
108 ThreadState& state = workerState[workerIndex];
109
110 CHECK_EQUAL((uint32)0, state.lowProcessed);
111
112 state.normalProcessed++;
113
114 //if state is set to HIGH tasks, change state to NORMAL tasks
115 if (state.taskPrio == 0)
116 {
117 state.taskPrio = 1;
118 switchCountToNormal.IncFetch();
119 }
120
121 // Check we in right state (executing NORMAL priority tasks)
122 CHECK_EQUAL((uint32)1, state.taskPrio);
123 }
124 };
125
126 struct TaskLow
127 {
128 MT_DECLARE_TASK(TaskLow, MT::StackRequirements::STANDARD, MT::TaskPriority::LOW, MT::Color::Blue);
129
130 uint32 id;
131
132 TaskLow(uint32 _id)
133 : id(_id)
134 {
135 }
136
137
138 void Do(MT::FiberContext& ctx)
139 {
140 uint32 workerIndex = ctx.GetThreadContext()->workerIndex;
141 MT_ASSERT(workerIndex < MT_ARRAY_SIZE(workerState), "Invalid worker index");
142 ThreadState& state = workerState[workerIndex];
143
144 state.lowProcessed++;
145
146 //if state is set to NORMAL tasks, change state to LOW tasks
147 if (state.taskPrio == 1)
148 {
149 state.taskPrio = 2;
150 switchCountToLow.IncFetch();
151 }
152
153 // Check we in right state (executing LOW priority tasks)
154 CHECK_EQUAL((uint32)2, state.taskPrio);
155 }
156 };
157
158
159
160
161 TEST(SimplePriorityTest)
162 {
163 MT::TaskPool<TaskLow, TASK_COUNT> lowPriorityTasksPool;
164 MT::TaskPool<TaskNormal, TASK_COUNT> normalPriorityTasksPool;
165 MT::TaskPool<TaskHigh, TASK_COUNT> highPriorityTasksPool;
166
167 // Disable task stealing (for testing purposes only)
168 #ifdef MT_INSTRUMENTED_BUILD
169 MT::TaskScheduler scheduler(0, nullptr, nullptr, MT::TaskStealingMode::DISABLED);
170 #else
171 MT::TaskScheduler scheduler(0, nullptr, MT::TaskStealingMode::DISABLED);
172 #endif
173
174 // Use task handles to add multiple tasks with different priorities in one RunAsync call
175 MT::TaskHandle taskHandles[TASK_COUNT*3];
176
177 uint32 index = 0;
178 for(uint32 i = 0; i < TASK_COUNT; i++)
179 {
180 taskHandles[index] = lowPriorityTasksPool.Alloc(TaskLow(i));
181 index++;
182 }
183
184 for(uint32 i = 0; i < TASK_COUNT; i++)
185 {
186 taskHandles[index] = highPriorityTasksPool.Alloc(TaskHigh(i));
187 index++;
188 }
189
190 for(uint32 i = 0; i < TASK_COUNT; i++)
191 {
192 taskHandles[index] = normalPriorityTasksPool.Alloc(TaskNormal(i));
193 index++;
194 }
195
196 switchCountToNormal.Store(0);
197 switchCountToLow.Store(0);
198
199 for(uint32 i = 0; i < MT_ARRAY_SIZE(workerState); i++)
200 {
201 workerState[i].Reset();
202 }
203
204 scheduler.RunAsync(MT::TaskGroup::Default(), &taskHandles[0], MT_ARRAY_SIZE(taskHandles));
205 CHECK(scheduler.WaitAll(2000));
206
207 int32 workersCount = scheduler.GetWorkersCount();
208 float minTasksExecuted = (float)TASK_COUNT / (float)workersCount;
209 minTasksExecuted *= 0.95f;
210 uint32 minTasksExecutedThreshold = (uint32)minTasksExecuted;
211
212 uint32 lowProcessedTotal = 0;
213 uint32 normalProcessedTotal = 0;
214 uint32 highProcessedTotal = 0;
215
216 for(int32 j = 0; j < workersCount; j++)
217 {
218 lowProcessedTotal += workerState[j].lowProcessed;
219 normalProcessedTotal += workerState[j].normalProcessed;
220 highProcessedTotal += workerState[j].highProcessed;
221 }
222
223 CHECK_EQUAL(TASK_COUNT, lowProcessedTotal);
224 CHECK_EQUAL(TASK_COUNT, normalProcessedTotal);
225 CHECK_EQUAL(TASK_COUNT, highProcessedTotal);
226
227 for(int32 j = 0; j < workersCount; j++)
228 {
229 printf("worker #%d\n", j);
230
231 CHECK_EQUAL((uint32)2, workerState[j].taskPrio);
232 CHECK(workerState[j].lowProcessed >= minTasksExecutedThreshold);
233 CHECK(workerState[j].normalProcessed >= minTasksExecutedThreshold);
234 CHECK(workerState[j].highProcessed >= minTasksExecutedThreshold);
235
236 printf(" low : %d\n", workerState[j].lowProcessed);
237 printf(" normal : %d\n", workerState[j].normalProcessed);
238 printf(" high : %d\n", workerState[j].highProcessed);
239 }
240
241 //
242 // Every worker thread can't change state more than once.
243 //
244 CHECK_EQUAL(workersCount, switchCountToNormal.Load());
245 CHECK_EQUAL(workersCount, switchCountToLow.Load());
246
247
248 }
249 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
250 }
251
252