--- /dev/null
+/*-------------------------------------------------------------------------
+ * 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
--- /dev/null
+#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