Add de::SpinBarrier
authorPyry Haulos <phaulos@google.com>
Thu, 9 Apr 2015 23:01:58 +0000 (16:01 -0700)
committerPyry Haulos <phaulos@google.com>
Mon, 13 Apr 2015 16:26:14 +0000 (09:26 -0700)
SpinBarrier provides convenient cross-thread barriers.

Change-Id: I70eac2ed07b2c123d9709ecf5bbe284f35771204

Android.mk
framework/delibs/decpp/CMakeLists.txt
framework/delibs/decpp/deSpinBarrier.cpp [new file with mode: 0644]
framework/delibs/decpp/deSpinBarrier.hpp [new file with mode: 0644]
modules/internal/ditDelibsTests.cpp

index 2115e14..9ace5f1 100644 (file)
@@ -98,6 +98,7 @@ LOCAL_SRC_FILES := \
        framework/delibs/decpp/deSemaphore.cpp \
        framework/delibs/decpp/deSharedPtr.cpp \
        framework/delibs/decpp/deSocket.cpp \
+       framework/delibs/decpp/deSpinBarrier.cpp \
        framework/delibs/decpp/deSTLUtil.cpp \
        framework/delibs/decpp/deStringUtil.cpp \
        framework/delibs/decpp/deThread.cpp \
index f5120ef..974c385 100644 (file)
@@ -55,6 +55,8 @@ set(DECPP_SRCS
        deThreadSafeRingBuffer.hpp
        deUniquePtr.cpp
        deUniquePtr.hpp
+       deSpinBarrier.cpp
+       deSpinBarrier.hpp
        )
 
 add_library(decpp STATIC ${DECPP_SRCS})
diff --git a/framework/delibs/decpp/deSpinBarrier.cpp b/framework/delibs/decpp/deSpinBarrier.cpp
new file mode 100644 (file)
index 0000000..150bbc2
--- /dev/null
@@ -0,0 +1,196 @@
+/*-------------------------------------------------------------------------
+ * 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
diff --git a/framework/delibs/decpp/deSpinBarrier.hpp b/framework/delibs/decpp/deSpinBarrier.hpp
new file mode 100644 (file)
index 0000000..6679f8d
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef _DESPINBARRIER_HPP
+#define _DESPINBARRIER_HPP
+/*-------------------------------------------------------------------------
+ * 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 "deDefs.hpp"
+#include "deAtomic.h"
+
+namespace de
+{
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Cross-thread barrier
+ *
+ * SpinBarrier provides barrier implementation that uses spin loop for
+ * waiting for other threads. Threads may choose to wait in tight loop
+ * (WAIT_MODE_BUSY) or yield between iterations (WAIT_MODE_YIELD).
+ *//*--------------------------------------------------------------------*/
+class SpinBarrier
+{
+public:
+       enum WaitMode
+       {
+               WAIT_MODE_BUSY = 0,
+               WAIT_MODE_YIELD,
+
+               WAIT_MODE_LAST
+       };
+
+                                               SpinBarrier             (deInt32 numThreads);
+                                               ~SpinBarrier    (void);
+
+       void                            sync                    (WaitMode mode);
+
+private:
+                                               SpinBarrier             (const SpinBarrier&);
+       SpinBarrier                     operator=               (const SpinBarrier&);
+
+       const deInt32           m_numThreads;
+       volatile deInt32        m_numEntered;
+       volatile deInt32        m_numLeaving;
+};
+
+void   SpinBarrier_selfTest    (void);
+
+} // de
+
+#endif // _DESPINBARRIER_HPP
index bdda4ff..df47341 100644 (file)
@@ -52,6 +52,7 @@
 #include "deCommandLine.hpp"
 #include "deArrayBuffer.hpp"
 #include "deStringUtil.hpp"
+#include "deSpinBarrier.hpp"
 
 namespace dit
 {
@@ -159,6 +160,7 @@ public:
                addChild(new SelfCheckCase(m_testCtx, "commandline",                            "de::cmdline::selfTest()",                              de::cmdline::selfTest));
                addChild(new SelfCheckCase(m_testCtx, "array_buffer",                           "de::ArrayBuffer_selfTest()",                   de::ArrayBuffer_selfTest));
                addChild(new SelfCheckCase(m_testCtx, "string_util",                            "de::StringUtil_selfTest()",                    de::StringUtil_selfTest));
+               addChild(new SelfCheckCase(m_testCtx, "spin_barrier",                           "de::SpinBarrier_selfTest()",                   de::SpinBarrier_selfTest));
        }
 };