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