Add de::AppendList<T>
authorPyry Haulos <phaulos@google.com>
Fri, 11 Dec 2015 22:24:21 +0000 (14:24 -0800)
committerPyry Haulos <phaulos@google.com>
Wed, 16 Dec 2015 01:03:29 +0000 (17:03 -0800)
AppendList provides fast append-only data structure that can be updated
from multiple threads simultaneously. Suitable for logging for example.

Change-Id: I4f6ee5f0e040695ffcd2006330dab1a4de81e9e4

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

index 1619790..ba09272 100644 (file)
@@ -81,6 +81,7 @@ LOCAL_SRC_FILES := \
        framework/delibs/debase/deRandom.c \
        framework/delibs/debase/deString.c \
        framework/delibs/debase/deSha1.c \
+       framework/delibs/decpp/deAppendList.cpp \
        framework/delibs/decpp/deArrayBuffer.cpp \
        framework/delibs/decpp/deArrayUtil.cpp \
        framework/delibs/decpp/deBlockBuffer.cpp \
index 4994b2d..1266335 100644 (file)
@@ -5,6 +5,8 @@ if (NOT DE_DEFS)
 endif ()
 
 set(DECPP_SRCS
+       deAppendList.cpp
+       deAppendList.hpp
        deArrayBuffer.cpp
        deArrayBuffer.hpp
        deArrayUtil.cpp
diff --git a/framework/delibs/decpp/deAppendList.cpp b/framework/delibs/decpp/deAppendList.cpp
new file mode 100644 (file)
index 0000000..ed7ef74
--- /dev/null
@@ -0,0 +1,155 @@
+/*-------------------------------------------------------------------------
+ * 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 Fast ordered append-only container
+ *//*--------------------------------------------------------------------*/
+
+#include "deAppendList.hpp"
+#include "deThread.hpp"
+#include "deSpinBarrier.hpp"
+#include "deSharedPtr.hpp"
+
+#include <vector>
+#include <algorithm>
+
+namespace de
+{
+
+namespace
+{
+
+using std::vector;
+
+struct TestElem
+{
+       deUint32        threadNdx;
+       deUint32        elemNdx;
+
+       TestElem (deUint32 threadNdx_, deUint32 elemNdx_)
+               : threadNdx     (threadNdx_)
+               , elemNdx       (elemNdx_)
+       {}
+
+       TestElem (void)
+               : threadNdx     (0)
+               , elemNdx       (0)
+       {}
+};
+
+struct SharedState
+{
+       deUint32                                numElements;
+       SpinBarrier                             barrier;
+       AppendList<TestElem>    testList;
+
+       SharedState (deUint32 numThreads, deUint32 numElements_, deUint32 numElementsHint)
+               : numElements   (numElements_)
+               , barrier               (numThreads)
+               , testList              (numElementsHint)
+       {}
+};
+
+class TestThread : public Thread
+{
+public:
+       TestThread (SharedState* shared, deUint32 threadNdx)
+               : m_shared              (shared)
+               , m_threadNdx   (threadNdx)
+       {}
+
+       void run (void)
+       {
+               const deUint32  syncPerElems    = 10000;
+
+               for (deUint32 elemNdx = 0; elemNdx < m_shared->numElements; elemNdx++)
+               {
+                       if (elemNdx % syncPerElems == 0)
+                               m_shared->barrier.sync(SpinBarrier::WAIT_MODE_AUTO);
+
+                       m_shared->testList.append(TestElem(m_threadNdx, elemNdx));
+               }
+       }
+
+private:
+       SharedState* const      m_shared;
+       const deUint32          m_threadNdx;
+};
+
+typedef SharedPtr<TestThread> TestThreadSp;
+
+void runAppendListTest (deUint32 numThreads, deUint32 numElements, deUint32 numElementsHint)
+{
+       SharedState                             sharedState             (numThreads, numElements, numElementsHint);
+       vector<TestThreadSp>    threads                 (numThreads);
+
+       for (deUint32 threadNdx = 0; threadNdx < numThreads; ++threadNdx)
+       {
+               threads[threadNdx] = TestThreadSp(new TestThread(&sharedState, threadNdx));
+               threads[threadNdx]->start();
+       }
+
+       for (deUint32 threadNdx = 0; threadNdx < numThreads; ++threadNdx)
+               threads[threadNdx]->join();
+
+       DE_TEST_ASSERT(sharedState.testList.size() == (size_t)numElements*(size_t)numThreads);
+
+       {
+               vector<deUint32>        countByThread   (numThreads);
+
+               std::fill(countByThread.begin(), countByThread.end(), 0);
+
+               for (AppendList<TestElem>::const_iterator elemIter = sharedState.testList.begin();
+                        elemIter != sharedState.testList.end();
+                        ++elemIter)
+               {
+                       const TestElem& elem    = *elemIter;
+
+                       DE_TEST_ASSERT(de::inBounds(elem.threadNdx, 0u, numThreads));
+                       DE_TEST_ASSERT(countByThread[elem.threadNdx] == elem.elemNdx);
+
+                       countByThread[elem.threadNdx] += 1;
+               }
+
+               for (deUint32 threadNdx = 0; threadNdx < numThreads; ++threadNdx)
+                       DE_TEST_ASSERT(countByThread[threadNdx] == numElements);
+       }
+}
+
+} // anonymous
+
+void AppendList_selfTest (void)
+{
+       // Single-threaded
+       runAppendListTest(1, 1000, 500);
+       runAppendListTest(1, 1000, 2000);
+       runAppendListTest(1, 35, 1);
+
+       // Multi-threaded
+       runAppendListTest(2, 10000, 500);
+       runAppendListTest(2, 100, 10);
+
+       if (deGetNumAvailableLogicalCores() >= 4)
+       {
+               runAppendListTest(4, 10000, 500);
+               runAppendListTest(4, 100, 10);
+       }
+}
+
+} // de
diff --git a/framework/delibs/decpp/deAppendList.hpp b/framework/delibs/decpp/deAppendList.hpp
new file mode 100644 (file)
index 0000000..4c37767
--- /dev/null
@@ -0,0 +1,261 @@
+#ifndef _DEAPPENDLIST_HPP
+#define _DEAPPENDLIST_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 Fast ordered append-only container
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.hpp"
+#include "deAtomic.h"
+#include "deThread.h"
+#include "deMemory.h"
+#include "deInt32.h"
+
+namespace de
+{
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Fast ordered append-only container
+ *
+ * AppendList provides data structure for recording ordered list of elements
+ * quickly, while still providing good sequential read access speed.
+ * It is good for example logging.
+ *
+ * AppendList allocates memory in blocks of blockSize elements. Choosing
+ * too small blockSize will affect performance.
+ *
+ * Elements can be appended from multiple threads simultaneously but if
+ * current block runs out, allocation of next block will happen in a single
+ * thread and block others from inserting further elements until completed.
+ * For that reason shared AppendList should not be used if there is a lot
+ * of contention and instead per-thread AppendList's are recommended.
+ *//*--------------------------------------------------------------------*/
+template<typename ElementType>
+class AppendList
+{
+public:
+                                                               AppendList              (size_t blockSize);
+                                                               ~AppendList             (void);
+
+       void                                            append                  (const ElementType& value);
+
+       size_t                                          size                    (void) const { return m_numElements;    }
+
+private:
+                                                               AppendList              (const AppendList<ElementType>&);
+       AppendList<ElementType>&        operator=               (const AppendList<ElementType>&);
+
+       struct Block
+       {
+               const size_t            blockNdx;
+               ElementType*            elements;
+               Block* volatile         next;
+
+               Block (size_t blockNdx_, size_t size)
+                       : blockNdx      (blockNdx_)
+                       , elements      (reinterpret_cast<ElementType*>(deAlignedMalloc(sizeof(ElementType)*size,
+                                                                                                                                               deAlign32((deUint32)alignOf<ElementType>(), (deUint32)sizeof(void*)))))
+                       , next          (DE_NULL)
+               {
+               }
+
+               ~Block (void)
+               {
+                       deAlignedFree(reinterpret_cast<void*>(elements));
+               }
+       };
+
+       const size_t                            m_blockSize;
+       volatile size_t                         m_numElements;
+       Block*                                          m_first;
+       Block* volatile                         m_last;
+
+public:
+       template<typename CompatibleType>
+       class Iterator
+       {
+       public:
+                                                                       Iterator                                                (Block* curBlock_, size_t blockSize_, size_t slotNdx_)
+                                                                                                                                               : m_curBlock    (curBlock_)
+                                                                                                                                               , m_blockSize   (blockSize_)
+                                                                                                                                               , m_slotNdx             (slotNdx_)
+               {}
+
+               bool                                            operator!=                                              (const Iterator<CompatibleType>& other) const
+               {
+                       return m_curBlock != other.m_curBlock || m_slotNdx != other.m_slotNdx;
+               }
+               bool                                            operator==                                              (const Iterator<CompatibleType>& other) const
+               {
+                       return m_curBlock == other.m_curBlock && m_slotNdx == other.m_slotNdx;
+               }
+
+               Iterator<CompatibleType>&       operator++                                              (void)
+               {
+                       ++m_slotNdx;
+
+                       if (m_slotNdx == m_blockSize)
+                       {
+                               m_slotNdx = 0;
+                               m_curBlock = m_curBlock->next;
+                       }
+
+                       return *this;
+               }
+
+               Iterator<CompatibleType>        operator++                                              (int) const
+               {
+                       Iterator<CompatibleType> copy(*this);
+                       return ++copy;
+               }
+
+               CompatibleType&                         operator*                                               (void) const
+               {
+                       return m_curBlock->elements[m_slotNdx];
+               }
+
+               operator                                        Iterator<const CompatibleType>  (void) const
+               {
+                       return Iterator<const CompatibleType>(m_curBlock, m_blockSize, m_slotNdx);
+               }
+
+       private:
+               Block*                  m_curBlock;
+               size_t                  m_blockSize;
+               size_t                  m_slotNdx;
+       };
+
+       typedef Iterator<const ElementType>     const_iterator;
+       typedef Iterator<ElementType>           iterator;
+
+       const_iterator                          begin                   (void) const;
+       iterator                                        begin                   (void);
+
+       const_iterator                          end                             (void) const;
+       iterator                                        end                             (void);
+};
+
+template<typename ElementType>
+AppendList<ElementType>::AppendList (size_t blockSize)
+       : m_blockSize   (blockSize)
+       , m_numElements (0)
+       , m_first               (new Block(0, blockSize))
+       , m_last                (m_first)
+{
+}
+
+template<typename ElementType>
+AppendList<ElementType>::~AppendList (void)
+{
+       size_t  elementNdx      = 0;
+       Block*  curBlock        = m_first;
+
+       while (curBlock)
+       {
+               Block* const    delBlock        = curBlock;
+
+               curBlock = delBlock->next;
+
+               // Call destructor for allocated elements
+               for (; elementNdx < min(m_numElements, delBlock->blockNdx*m_blockSize); ++elementNdx)
+                       delBlock->elements[elementNdx%m_blockSize].~ElementType();
+
+               delete delBlock;
+       }
+}
+
+template<typename ElementType>
+void AppendList<ElementType>::append (const ElementType& value)
+{
+       // Fetch curBlock first before allocating slot. Otherwise m_last might get updated before
+       // this thread gets chance of reading it, leading to curBlock->blockNdx > blockNdx.
+       Block*                  curBlock        = m_last;
+
+       deMemoryReadWriteFence();
+
+       {
+               const size_t    elementNdx      = deAtomicIncrementUSize(&m_numElements) - 1;
+               const size_t    blockNdx        = elementNdx / m_blockSize;
+               const size_t    slotNdx         = elementNdx - (blockNdx * m_blockSize);
+
+               while (curBlock->blockNdx != blockNdx)
+               {
+                       if (curBlock->next)
+                               curBlock = curBlock->next;
+                       else
+                       {
+                               // Other thread(s) are currently allocating additional block(s)
+                               deYield();
+                       }
+               }
+
+               // Did we allocate last slot? If so, add a new block
+               if (slotNdx+1 == m_blockSize)
+               {
+                       Block* const    newBlock        = new Block(blockNdx+1, m_blockSize);
+
+                       deMemoryReadWriteFence();
+
+                       // At this point if any other thread is trying to allocate more blocks
+                       // they are being blocked by curBlock->next being null. This guarantees
+                       // that this thread has exclusive modify access to m_last.
+                       m_last = newBlock;
+                       deMemoryReadWriteFence();
+
+                       // At this point other threads might have skipped to newBlock, but we
+                       // still have exclusive modify access to curBlock->next.
+                       curBlock->next = newBlock;
+                       deMemoryReadWriteFence();
+               }
+
+               new (&curBlock->elements[slotNdx]) ElementType(value);
+       }
+}
+
+template<typename ElementType>
+typename AppendList<ElementType>::const_iterator AppendList<ElementType>::begin (void) const
+{
+       return const_iterator(m_first, m_blockSize, 0);
+}
+
+template<typename ElementType>
+typename AppendList<ElementType>::iterator AppendList<ElementType>::begin (void)
+{
+       return iterator(m_first, m_blockSize, 0);
+}
+
+template<typename ElementType>
+typename AppendList<ElementType>::const_iterator AppendList<ElementType>::end (void) const
+{
+       return const_iterator(m_last, m_blockSize, m_numElements%m_blockSize);
+}
+
+template<typename ElementType>
+typename AppendList<ElementType>::iterator AppendList<ElementType>::end (void)
+{
+       return iterator(m_last, m_blockSize, m_numElements%m_blockSize);
+}
+
+void   AppendList_selfTest             (void);
+
+} // de
+
+#endif // _DEAPPENDLIST_HPP
index 85f744a..e3083de 100644 (file)
@@ -60,6 +60,7 @@
 #include "deStringUtil.hpp"
 #include "deSpinBarrier.hpp"
 #include "deSTLUtil.hpp"
+#include "deAppendList.hpp"
 
 namespace dit
 {
@@ -186,6 +187,7 @@ public:
                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));
                addChild(new SelfCheckCase(m_testCtx, "stl_util",                                       "de::STLUtil_selfTest()",                               de::STLUtil_selfTest));
+               addChild(new SelfCheckCase(m_testCtx, "append_list",                            "de::AppendList_selfTest()",                    de::AppendList_selfTest));
        }
 };