blob: 21c9f67edc4e5069a32702745d4a92ea0b634620 [file] [log] [blame]
#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; }
void clear (void);
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];
}
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+1)*m_blockSize); ++elementNdx)
delBlock->elements[elementNdx%m_blockSize].~ElementType();
delete delBlock;
}
DE_ASSERT(elementNdx == m_numElements);
}
template<typename ElementType>
void AppendList<ElementType>::clear (void)
{
// \todo [2016-03-28 pyry] Make thread-safe, if possible
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+1)*m_blockSize); ++elementNdx)
delBlock->elements[elementNdx%m_blockSize].~ElementType();
if (delBlock != m_first)
delete delBlock;
}
DE_ASSERT(elementNdx == m_numElements);
m_numElements = 0;
m_first->next = DE_NULL;
m_last = m_first;
}
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