Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 1 | /*------------------------------------------------------------------------- |
| 2 | * drawElements C++ Base Library |
| 3 | * ----------------------------- |
| 4 | * |
| 5 | * Copyright 2015 The Android Open Source Project |
| 6 | * |
| 7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | * you may not use this file except in compliance with the License. |
| 9 | * You may obtain a copy of the License at |
| 10 | * |
| 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | * |
| 13 | * Unless required by applicable law or agreed to in writing, software |
| 14 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | * See the License for the specific language governing permissions and |
| 17 | * limitations under the License. |
| 18 | * |
| 19 | *//*! |
| 20 | * \file |
| 21 | * \brief Cross-thread barrier. |
| 22 | *//*--------------------------------------------------------------------*/ |
| 23 | |
| 24 | #include "deSpinBarrier.hpp" |
| 25 | #include "deThread.hpp" |
| 26 | #include "deRandom.hpp" |
| 27 | #include "deInt32.h" |
| 28 | |
| 29 | #include <vector> |
| 30 | |
| 31 | namespace de |
| 32 | { |
| 33 | |
| 34 | SpinBarrier::SpinBarrier (deInt32 numThreads) |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 35 | : m_numCores (deGetNumAvailableLogicalCores()) |
| 36 | , m_numThreads (numThreads) |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 37 | , m_numEntered (0) |
| 38 | , m_numLeaving (0) |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 39 | , m_numRemoved (0) |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 40 | { |
| 41 | DE_ASSERT(numThreads > 0); |
| 42 | } |
| 43 | |
| 44 | SpinBarrier::~SpinBarrier (void) |
| 45 | { |
| 46 | DE_ASSERT(m_numEntered == 0 && m_numLeaving == 0); |
| 47 | } |
| 48 | |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 49 | void SpinBarrier::reset (deUint32 numThreads) |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 50 | { |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 51 | // If last threads were removed, m_numEntered > 0 && m_numRemoved > 0 |
| 52 | DE_ASSERT(m_numLeaving == 0); |
| 53 | DE_ASSERT(numThreads > 0); |
| 54 | m_numThreads = numThreads; |
| 55 | m_numEntered = 0; |
| 56 | m_numLeaving = 0; |
| 57 | m_numRemoved = 0; |
| 58 | } |
| 59 | |
| 60 | inline SpinBarrier::WaitMode getWaitMode (SpinBarrier::WaitMode requested, deUint32 numCores, deInt32 numThreads) |
| 61 | { |
| 62 | if (requested == SpinBarrier::WAIT_MODE_AUTO) |
| 63 | return ((deUint32)numThreads <= numCores) ? SpinBarrier::WAIT_MODE_BUSY : SpinBarrier::WAIT_MODE_YIELD; |
| 64 | else |
| 65 | return requested; |
| 66 | } |
| 67 | |
| 68 | inline void wait (SpinBarrier::WaitMode mode) |
| 69 | { |
| 70 | DE_ASSERT(mode == SpinBarrier::WAIT_MODE_YIELD || mode == SpinBarrier::WAIT_MODE_BUSY); |
| 71 | |
| 72 | if (mode == SpinBarrier::WAIT_MODE_YIELD) |
| 73 | deYield(); |
| 74 | } |
| 75 | |
| 76 | void SpinBarrier::sync (WaitMode requestedMode) |
| 77 | { |
Jarkko Pöyry | e36eed7 | 2015-05-18 20:55:41 -0700 | [diff] [blame] | 78 | const deInt32 cachedNumThreads = m_numThreads; |
| 79 | const WaitMode waitMode = getWaitMode(requestedMode, m_numCores, cachedNumThreads); |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 80 | |
| 81 | deMemoryReadWriteFence(); |
| 82 | |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 83 | // m_numEntered must not be touched until all threads have had |
| 84 | // a chance to observe it being 0. |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 85 | if (m_numLeaving > 0) |
| 86 | { |
| 87 | for (;;) |
| 88 | { |
| 89 | if (m_numLeaving == 0) |
| 90 | break; |
| 91 | |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 92 | wait(waitMode); |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 93 | } |
| 94 | } |
| 95 | |
Jarkko Pöyry | e36eed7 | 2015-05-18 20:55:41 -0700 | [diff] [blame] | 96 | // If m_numRemoved > 0, m_numThreads will decrease. If m_numThreads is decreased |
| 97 | // just after atomicOp and before comparison, the branch could be taken by multiple |
| 98 | // threads. Since m_numThreads only changes if all threads are inside the spinbarrier, |
| 99 | // cached value at snapshotted at the beginning of the function will be equal for |
| 100 | // all threads. |
| 101 | if (deAtomicIncrement32(&m_numEntered) == cachedNumThreads) |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 102 | { |
Jarkko Pöyry | e36eed7 | 2015-05-18 20:55:41 -0700 | [diff] [blame] | 103 | // Release all waiting threads. Since this thread has not been removed, m_numLeaving will |
| 104 | // be >= 1 until m_numLeaving is decremented at the end of this function. |
Peter Kasting | 87c2ea8 | 2022-09-08 19:00:54 +0000 | [diff] [blame^] | 105 | m_numThreads = m_numThreads - m_numRemoved; |
| 106 | m_numLeaving = m_numThreads; |
| 107 | m_numRemoved = 0; |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 108 | |
| 109 | deMemoryReadWriteFence(); |
Peter Kasting | 87c2ea8 | 2022-09-08 19:00:54 +0000 | [diff] [blame^] | 110 | m_numEntered = 0; |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 111 | } |
| 112 | else |
| 113 | { |
| 114 | for (;;) |
| 115 | { |
| 116 | if (m_numEntered == 0) |
| 117 | break; |
| 118 | |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 119 | wait(waitMode); |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 120 | } |
| 121 | } |
| 122 | |
| 123 | deAtomicDecrement32(&m_numLeaving); |
| 124 | deMemoryReadWriteFence(); |
| 125 | } |
| 126 | |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 127 | void SpinBarrier::removeThread (WaitMode requestedMode) |
| 128 | { |
Jarkko Pöyry | e36eed7 | 2015-05-18 20:55:41 -0700 | [diff] [blame] | 129 | const deInt32 cachedNumThreads = m_numThreads; |
| 130 | const WaitMode waitMode = getWaitMode(requestedMode, m_numCores, cachedNumThreads); |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 131 | |
| 132 | // Wait for other threads exiting previous barrier |
| 133 | if (m_numLeaving > 0) |
| 134 | { |
| 135 | for (;;) |
| 136 | { |
| 137 | if (m_numLeaving == 0) |
| 138 | break; |
| 139 | |
| 140 | wait(waitMode); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | // Ask for last thread entering barrier to adjust thread count |
| 145 | deAtomicIncrement32(&m_numRemoved); |
| 146 | |
Jarkko Pöyry | e36eed7 | 2015-05-18 20:55:41 -0700 | [diff] [blame] | 147 | // See sync() - use cached value |
| 148 | if (deAtomicIncrement32(&m_numEntered) == cachedNumThreads) |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 149 | { |
Jarkko Pöyry | e36eed7 | 2015-05-18 20:55:41 -0700 | [diff] [blame] | 150 | // Release all waiting threads. |
Peter Kasting | 87c2ea8 | 2022-09-08 19:00:54 +0000 | [diff] [blame^] | 151 | m_numThreads = m_numThreads - m_numRemoved; |
| 152 | m_numLeaving = m_numThreads; |
| 153 | m_numRemoved = 0; |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 154 | |
| 155 | deMemoryReadWriteFence(); |
Peter Kasting | 87c2ea8 | 2022-09-08 19:00:54 +0000 | [diff] [blame^] | 156 | m_numEntered = 0; |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 157 | } |
| 158 | } |
| 159 | |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 160 | namespace |
| 161 | { |
| 162 | |
| 163 | void singleThreadTest (SpinBarrier::WaitMode mode) |
| 164 | { |
| 165 | SpinBarrier barrier(1); |
| 166 | |
| 167 | barrier.sync(mode); |
| 168 | barrier.sync(mode); |
| 169 | barrier.sync(mode); |
| 170 | } |
| 171 | |
| 172 | class TestThread : public de::Thread |
| 173 | { |
| 174 | public: |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 175 | TestThread (SpinBarrier& barrier, volatile deInt32* sharedVar, int numThreads, int threadNdx) |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 176 | : m_barrier (barrier) |
| 177 | , m_sharedVar (sharedVar) |
| 178 | , m_numThreads (numThreads) |
| 179 | , m_threadNdx (threadNdx) |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 180 | , m_busyOk ((deUint32)m_numThreads <= deGetNumAvailableLogicalCores()) |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 181 | { |
| 182 | } |
| 183 | |
| 184 | void run (void) |
| 185 | { |
| 186 | const int numIters = 10000; |
| 187 | de::Random rnd (deInt32Hash(m_numThreads) ^ deInt32Hash(m_threadNdx)); |
| 188 | |
| 189 | for (int iterNdx = 0; iterNdx < numIters; iterNdx++) |
| 190 | { |
| 191 | // Phase 1: count up |
| 192 | deAtomicIncrement32(m_sharedVar); |
| 193 | |
| 194 | // Verify |
| 195 | m_barrier.sync(getWaitMode(rnd)); |
| 196 | |
| 197 | DE_TEST_ASSERT(*m_sharedVar == m_numThreads); |
| 198 | |
| 199 | m_barrier.sync(getWaitMode(rnd)); |
| 200 | |
| 201 | // Phase 2: count down |
| 202 | deAtomicDecrement32(m_sharedVar); |
| 203 | |
| 204 | // Verify |
| 205 | m_barrier.sync(getWaitMode(rnd)); |
| 206 | |
| 207 | DE_TEST_ASSERT(*m_sharedVar == 0); |
| 208 | |
| 209 | m_barrier.sync(getWaitMode(rnd)); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | private: |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 214 | SpinBarrier& m_barrier; |
| 215 | volatile deInt32* const m_sharedVar; |
| 216 | const int m_numThreads; |
| 217 | const int m_threadNdx; |
| 218 | const bool m_busyOk; |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 219 | |
| 220 | SpinBarrier::WaitMode getWaitMode (de::Random& rnd) |
| 221 | { |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 222 | static const SpinBarrier::WaitMode s_allModes[] = |
| 223 | { |
| 224 | SpinBarrier::WAIT_MODE_YIELD, |
| 225 | SpinBarrier::WAIT_MODE_AUTO, |
| 226 | SpinBarrier::WAIT_MODE_BUSY, |
| 227 | }; |
| 228 | const int numModes = DE_LENGTH_OF_ARRAY(s_allModes) - (m_busyOk ? 0 : 1); |
| 229 | |
| 230 | return rnd.choose<SpinBarrier::WaitMode>(DE_ARRAY_BEGIN(s_allModes), DE_ARRAY_BEGIN(s_allModes) + numModes); |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 231 | } |
| 232 | }; |
| 233 | |
| 234 | void multiThreadTest (int numThreads) |
| 235 | { |
| 236 | SpinBarrier barrier (numThreads); |
| 237 | volatile deInt32 sharedVar = 0; |
| 238 | std::vector<TestThread*> threads (numThreads, static_cast<TestThread*>(DE_NULL)); |
| 239 | |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 240 | for (int ndx = 0; ndx < numThreads; ndx++) |
| 241 | { |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 242 | threads[ndx] = new TestThread(barrier, &sharedVar, numThreads, ndx); |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 243 | DE_TEST_ASSERT(threads[ndx]); |
| 244 | threads[ndx]->start(); |
| 245 | } |
| 246 | |
| 247 | for (int ndx = 0; ndx < numThreads; ndx++) |
| 248 | { |
| 249 | threads[ndx]->join(); |
| 250 | delete threads[ndx]; |
| 251 | } |
| 252 | |
| 253 | DE_TEST_ASSERT(sharedVar == 0); |
| 254 | } |
| 255 | |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 256 | void singleThreadRemoveTest (SpinBarrier::WaitMode mode) |
| 257 | { |
| 258 | SpinBarrier barrier(3); |
| 259 | |
| 260 | barrier.removeThread(mode); |
| 261 | barrier.removeThread(mode); |
| 262 | barrier.sync(mode); |
| 263 | barrier.removeThread(mode); |
| 264 | |
| 265 | barrier.reset(1); |
| 266 | barrier.sync(mode); |
| 267 | |
| 268 | barrier.reset(2); |
| 269 | barrier.removeThread(mode); |
| 270 | barrier.sync(mode); |
| 271 | } |
| 272 | |
| 273 | class TestExitThread : public de::Thread |
| 274 | { |
| 275 | public: |
| 276 | TestExitThread (SpinBarrier& barrier, int numThreads, int threadNdx, SpinBarrier::WaitMode waitMode) |
| 277 | : m_barrier (barrier) |
| 278 | , m_numThreads (numThreads) |
| 279 | , m_threadNdx (threadNdx) |
| 280 | , m_waitMode (waitMode) |
| 281 | { |
| 282 | } |
| 283 | |
| 284 | void run (void) |
| 285 | { |
| 286 | const int numIters = 10000; |
| 287 | de::Random rnd (deInt32Hash(m_numThreads) ^ deInt32Hash(m_threadNdx) ^ deInt32Hash((deInt32)m_waitMode)); |
| 288 | const int invExitProb = 1000; |
| 289 | |
| 290 | for (int iterNdx = 0; iterNdx < numIters; iterNdx++) |
| 291 | { |
| 292 | if (rnd.getInt(0, invExitProb) == 0) |
| 293 | { |
| 294 | m_barrier.removeThread(m_waitMode); |
| 295 | break; |
| 296 | } |
| 297 | else |
| 298 | m_barrier.sync(m_waitMode); |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | private: |
| 303 | SpinBarrier& m_barrier; |
| 304 | const int m_numThreads; |
| 305 | const int m_threadNdx; |
| 306 | const SpinBarrier::WaitMode m_waitMode; |
| 307 | }; |
| 308 | |
| 309 | void multiThreadRemoveTest (int numThreads, SpinBarrier::WaitMode waitMode) |
| 310 | { |
| 311 | SpinBarrier barrier (numThreads); |
| 312 | std::vector<TestExitThread*> threads (numThreads, static_cast<TestExitThread*>(DE_NULL)); |
| 313 | |
| 314 | for (int ndx = 0; ndx < numThreads; ndx++) |
| 315 | { |
| 316 | threads[ndx] = new TestExitThread(barrier, numThreads, ndx, waitMode); |
| 317 | DE_TEST_ASSERT(threads[ndx]); |
| 318 | threads[ndx]->start(); |
| 319 | } |
| 320 | |
| 321 | for (int ndx = 0; ndx < numThreads; ndx++) |
| 322 | { |
| 323 | threads[ndx]->join(); |
| 324 | delete threads[ndx]; |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | } // anonymous |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 329 | |
| 330 | void SpinBarrier_selfTest (void) |
| 331 | { |
| 332 | singleThreadTest(SpinBarrier::WAIT_MODE_YIELD); |
| 333 | singleThreadTest(SpinBarrier::WAIT_MODE_BUSY); |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 334 | singleThreadTest(SpinBarrier::WAIT_MODE_AUTO); |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 335 | multiThreadTest(1); |
| 336 | multiThreadTest(2); |
| 337 | multiThreadTest(4); |
| 338 | multiThreadTest(8); |
| 339 | multiThreadTest(16); |
Pyry Haulos | 86ff8cd | 2015-04-27 05:57:55 -0700 | [diff] [blame] | 340 | |
| 341 | singleThreadRemoveTest(SpinBarrier::WAIT_MODE_YIELD); |
| 342 | singleThreadRemoveTest(SpinBarrier::WAIT_MODE_BUSY); |
| 343 | singleThreadRemoveTest(SpinBarrier::WAIT_MODE_AUTO); |
| 344 | multiThreadRemoveTest(1, SpinBarrier::WAIT_MODE_BUSY); |
| 345 | multiThreadRemoveTest(2, SpinBarrier::WAIT_MODE_AUTO); |
| 346 | multiThreadRemoveTest(4, SpinBarrier::WAIT_MODE_AUTO); |
| 347 | multiThreadRemoveTest(8, SpinBarrier::WAIT_MODE_AUTO); |
| 348 | multiThreadRemoveTest(16, SpinBarrier::WAIT_MODE_AUTO); |
| 349 | multiThreadRemoveTest(1, SpinBarrier::WAIT_MODE_YIELD); |
| 350 | multiThreadRemoveTest(2, SpinBarrier::WAIT_MODE_YIELD); |
| 351 | multiThreadRemoveTest(4, SpinBarrier::WAIT_MODE_YIELD); |
| 352 | multiThreadRemoveTest(8, SpinBarrier::WAIT_MODE_YIELD); |
| 353 | multiThreadRemoveTest(16, SpinBarrier::WAIT_MODE_YIELD); |
Pyry Haulos | 3f4cf9e | 2015-04-09 16:01:58 -0700 | [diff] [blame] | 354 | } |
| 355 | |
| 356 | } // de |