| /*------------------------------------------------------------------------- |
| * drawElements C++ Base Library |
| * ----------------------------- |
| * |
| * Copyright 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief Cross-thread barrier. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "deSpinBarrier.hpp" |
| #include "deThread.hpp" |
| #include "deRandom.hpp" |
| #include "deInt32.h" |
| |
| #include <vector> |
| |
| namespace de |
| { |
| |
| SpinBarrier::SpinBarrier (deInt32 numThreads) |
| : m_numThreads (numThreads) |
| , m_numEntered (0) |
| , m_numLeaving (0) |
| { |
| DE_ASSERT(numThreads > 0); |
| } |
| |
| SpinBarrier::~SpinBarrier (void) |
| { |
| DE_ASSERT(m_numEntered == 0 && m_numLeaving == 0); |
| } |
| |
| void SpinBarrier::sync (WaitMode mode) |
| { |
| DE_ASSERT(mode == WAIT_MODE_YIELD || mode == WAIT_MODE_BUSY); |
| |
| deMemoryReadWriteFence(); |
| |
| if (m_numLeaving > 0) |
| { |
| for (;;) |
| { |
| if (m_numLeaving == 0) |
| break; |
| |
| if (mode == WAIT_MODE_YIELD) |
| deYield(); |
| } |
| } |
| |
| if (deAtomicIncrement32(&m_numEntered) == m_numThreads) |
| { |
| m_numLeaving = m_numThreads; |
| deMemoryReadWriteFence(); |
| m_numEntered = 0; |
| } |
| else |
| { |
| for (;;) |
| { |
| if (m_numEntered == 0) |
| break; |
| |
| if (mode == WAIT_MODE_YIELD) |
| deYield(); |
| } |
| } |
| |
| deAtomicDecrement32(&m_numLeaving); |
| deMemoryReadWriteFence(); |
| } |
| |
| namespace |
| { |
| |
| void singleThreadTest (SpinBarrier::WaitMode mode) |
| { |
| SpinBarrier barrier(1); |
| |
| barrier.sync(mode); |
| barrier.sync(mode); |
| barrier.sync(mode); |
| } |
| |
| class TestThread : public de::Thread |
| { |
| public: |
| TestThread (SpinBarrier& barrier, volatile deInt32* sharedVar, int numThreads, int threadNdx, bool busyOk) |
| : m_barrier (barrier) |
| , m_sharedVar (sharedVar) |
| , m_numThreads (numThreads) |
| , m_threadNdx (threadNdx) |
| , m_busyOk (busyOk) |
| { |
| } |
| |
| void run (void) |
| { |
| const int numIters = 10000; |
| de::Random rnd (deInt32Hash(m_numThreads) ^ deInt32Hash(m_threadNdx)); |
| |
| for (int iterNdx = 0; iterNdx < numIters; iterNdx++) |
| { |
| // Phase 1: count up |
| deAtomicIncrement32(m_sharedVar); |
| |
| // Verify |
| m_barrier.sync(getWaitMode(rnd)); |
| |
| DE_TEST_ASSERT(*m_sharedVar == m_numThreads); |
| |
| m_barrier.sync(getWaitMode(rnd)); |
| |
| // Phase 2: count down |
| deAtomicDecrement32(m_sharedVar); |
| |
| // Verify |
| m_barrier.sync(getWaitMode(rnd)); |
| |
| DE_TEST_ASSERT(*m_sharedVar == 0); |
| |
| m_barrier.sync(getWaitMode(rnd)); |
| } |
| } |
| |
| private: |
| SpinBarrier& m_barrier; |
| volatile deInt32* m_sharedVar; |
| int m_numThreads; |
| int m_threadNdx; |
| bool m_busyOk; |
| |
| SpinBarrier::WaitMode getWaitMode (de::Random& rnd) |
| { |
| if (m_busyOk && rnd.getBool()) |
| return SpinBarrier::WAIT_MODE_BUSY; |
| else |
| return SpinBarrier::WAIT_MODE_YIELD; |
| } |
| }; |
| |
| void multiThreadTest (int numThreads) |
| { |
| SpinBarrier barrier (numThreads); |
| volatile deInt32 sharedVar = 0; |
| std::vector<TestThread*> threads (numThreads, static_cast<TestThread*>(DE_NULL)); |
| |
| // Going over logical cores with busy-waiting will cause priority inversion and make tests take |
| // excessive amount of time. Use busy waiting only when number of threads is at most one per |
| // core. |
| const bool busyOk = (deUint32)numThreads <= deGetNumAvailableLogicalCores(); |
| |
| for (int ndx = 0; ndx < numThreads; ndx++) |
| { |
| threads[ndx] = new TestThread(barrier, &sharedVar, numThreads, ndx, busyOk); |
| DE_TEST_ASSERT(threads[ndx]); |
| threads[ndx]->start(); |
| } |
| |
| for (int ndx = 0; ndx < numThreads; ndx++) |
| { |
| threads[ndx]->join(); |
| delete threads[ndx]; |
| } |
| |
| DE_TEST_ASSERT(sharedVar == 0); |
| } |
| |
| } // namespace |
| |
| void SpinBarrier_selfTest (void) |
| { |
| singleThreadTest(SpinBarrier::WAIT_MODE_YIELD); |
| singleThreadTest(SpinBarrier::WAIT_MODE_BUSY); |
| multiThreadTest(1); |
| multiThreadTest(2); |
| multiThreadTest(4); |
| multiThreadTest(8); |
| multiThreadTest(16); |
| } |
| |
| } // de |