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 */ 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