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
SpinBarrier(deInt32 numThreads)34 SpinBarrier::SpinBarrier (deInt32 numThreads)
35 : m_numThreads (numThreads)
36 , m_numEntered (0)
37 , m_numLeaving (0)
38 {
39 DE_ASSERT(numThreads > 0);
40 }
41
~SpinBarrier(void)42 SpinBarrier::~SpinBarrier (void)
43 {
44 DE_ASSERT(m_numEntered == 0 && m_numLeaving == 0);
45 }
46
sync(WaitMode mode)47 void SpinBarrier::sync (WaitMode mode)
48 {
49 DE_ASSERT(mode == WAIT_MODE_YIELD || mode == WAIT_MODE_BUSY);
50
51 deMemoryReadWriteFence();
52
53 if (m_numLeaving > 0)
54 {
55 for (;;)
56 {
57 if (m_numLeaving == 0)
58 break;
59
60 if (mode == WAIT_MODE_YIELD)
61 deYield();
62 }
63 }
64
65 if (deAtomicIncrement32(&m_numEntered) == m_numThreads)
66 {
67 m_numLeaving = m_numThreads;
68 deMemoryReadWriteFence();
69 m_numEntered = 0;
70 }
71 else
72 {
73 for (;;)
74 {
75 if (m_numEntered == 0)
76 break;
77
78 if (mode == WAIT_MODE_YIELD)
79 deYield();
80 }
81 }
82
83 deAtomicDecrement32(&m_numLeaving);
84 deMemoryReadWriteFence();
85 }
86
87 namespace
88 {
89
singleThreadTest(SpinBarrier::WaitMode mode)90 void singleThreadTest (SpinBarrier::WaitMode mode)
91 {
92 SpinBarrier barrier(1);
93
94 barrier.sync(mode);
95 barrier.sync(mode);
96 barrier.sync(mode);
97 }
98
99 class TestThread : public de::Thread
100 {
101 public:
TestThread(SpinBarrier & barrier,volatile deInt32 * sharedVar,int numThreads,int threadNdx,bool busyOk)102 TestThread (SpinBarrier& barrier, volatile deInt32* sharedVar, int numThreads, int threadNdx, bool busyOk)
103 : m_barrier (barrier)
104 , m_sharedVar (sharedVar)
105 , m_numThreads (numThreads)
106 , m_threadNdx (threadNdx)
107 , m_busyOk (busyOk)
108 {
109 }
110
run(void)111 void run (void)
112 {
113 const int numIters = 10000;
114 de::Random rnd (deInt32Hash(m_numThreads) ^ deInt32Hash(m_threadNdx));
115
116 for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
117 {
118 // Phase 1: count up
119 deAtomicIncrement32(m_sharedVar);
120
121 // Verify
122 m_barrier.sync(getWaitMode(rnd));
123
124 DE_TEST_ASSERT(*m_sharedVar == m_numThreads);
125
126 m_barrier.sync(getWaitMode(rnd));
127
128 // Phase 2: count down
129 deAtomicDecrement32(m_sharedVar);
130
131 // Verify
132 m_barrier.sync(getWaitMode(rnd));
133
134 DE_TEST_ASSERT(*m_sharedVar == 0);
135
136 m_barrier.sync(getWaitMode(rnd));
137 }
138 }
139
140 private:
141 SpinBarrier& m_barrier;
142 volatile deInt32* m_sharedVar;
143 int m_numThreads;
144 int m_threadNdx;
145 bool m_busyOk;
146
getWaitMode(de::Random & rnd)147 SpinBarrier::WaitMode getWaitMode (de::Random& rnd)
148 {
149 if (m_busyOk && rnd.getBool())
150 return SpinBarrier::WAIT_MODE_BUSY;
151 else
152 return SpinBarrier::WAIT_MODE_YIELD;
153 }
154 };
155
multiThreadTest(int numThreads)156 void multiThreadTest (int numThreads)
157 {
158 SpinBarrier barrier (numThreads);
159 volatile deInt32 sharedVar = 0;
160 std::vector<TestThread*> threads (numThreads, static_cast<TestThread*>(DE_NULL));
161
162 // Going over logical cores with busy-waiting will cause priority inversion and make tests take
163 // excessive amount of time. Use busy waiting only when number of threads is at most one per
164 // core.
165 const bool busyOk = (deUint32)numThreads <= deGetNumAvailableLogicalCores();
166
167 for (int ndx = 0; ndx < numThreads; ndx++)
168 {
169 threads[ndx] = new TestThread(barrier, &sharedVar, numThreads, ndx, busyOk);
170 DE_TEST_ASSERT(threads[ndx]);
171 threads[ndx]->start();
172 }
173
174 for (int ndx = 0; ndx < numThreads; ndx++)
175 {
176 threads[ndx]->join();
177 delete threads[ndx];
178 }
179
180 DE_TEST_ASSERT(sharedVar == 0);
181 }
182
183 } // namespace
184
SpinBarrier_selfTest(void)185 void SpinBarrier_selfTest (void)
186 {
187 singleThreadTest(SpinBarrier::WAIT_MODE_YIELD);
188 singleThreadTest(SpinBarrier::WAIT_MODE_BUSY);
189 multiThreadTest(1);
190 multiThreadTest(2);
191 multiThreadTest(4);
192 multiThreadTest(8);
193 multiThreadTest(16);
194 }
195
196 } // de
197