• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifndef _DEAPPENDLIST_HPP
2 #define _DEAPPENDLIST_HPP
3 /*-------------------------------------------------------------------------
4  * drawElements C++ Base Library
5  * -----------------------------
6  *
7  * Copyright 2015 The Android Open Source Project
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  *//*!
22  * \file
23  * \brief Fast ordered append-only container
24  *//*--------------------------------------------------------------------*/
25 
26 #include "deDefs.hpp"
27 #include "deAtomic.h"
28 #include "deThread.h"
29 #include "deMemory.h"
30 #include "deInt32.h"
31 
32 namespace de
33 {
34 
35 /*--------------------------------------------------------------------*//*!
36  * \brief Fast ordered append-only container
37  *
38  * AppendList provides data structure for recording ordered list of elements
39  * quickly, while still providing good sequential read access speed.
40  * It is good for example logging.
41  *
42  * AppendList allocates memory in blocks of blockSize elements. Choosing
43  * too small blockSize will affect performance.
44  *
45  * Elements can be appended from multiple threads simultaneously but if
46  * current block runs out, allocation of next block will happen in a single
47  * thread and block others from inserting further elements until completed.
48  * For that reason shared AppendList should not be used if there is a lot
49  * of contention and instead per-thread AppendList's are recommended.
50  *//*--------------------------------------------------------------------*/
51 template<typename ElementType>
52 class AppendList
53 {
54 public:
55 								AppendList		(size_t blockSize);
56 								~AppendList		(void);
57 
58 	void						append			(const ElementType& value);
59 
size(void) const60 	size_t						size			(void) const { return m_numElements;	}
61 
62 	void						clear			(void);
63 
64 private:
65 								AppendList		(const AppendList<ElementType>&);
66 	AppendList<ElementType>&	operator=		(const AppendList<ElementType>&);
67 
68 	struct Block
69 	{
70 		const size_t		blockNdx;
71 		ElementType*		elements;
72 		Block* volatile		next;
73 
Blockde::AppendList::Block74 		Block (size_t blockNdx_, size_t size)
75 			: blockNdx	(blockNdx_)
76 			, elements	(reinterpret_cast<ElementType*>(deAlignedMalloc(sizeof(ElementType)*size,
77 																		deAlign32((deUint32)alignOf<ElementType>(), (deUint32)sizeof(void*)))))
78 			, next		(DE_NULL)
79 		{
80 		}
81 
~Blockde::AppendList::Block82 		~Block (void)
83 		{
84 			deAlignedFree(reinterpret_cast<void*>(elements));
85 		}
86 	};
87 
88 	const size_t				m_blockSize;
89 	volatile size_t				m_numElements;
90 	Block*						m_first;
91 	Block* volatile				m_last;
92 
93 public:
94 	template<typename CompatibleType>
95 	class Iterator
96 	{
97 	public:
Iterator(Block * curBlock_,size_t blockSize_,size_t slotNdx_)98 									Iterator						(Block* curBlock_, size_t blockSize_, size_t slotNdx_)
99 																		: m_curBlock	(curBlock_)
100 																		, m_blockSize	(blockSize_)
101 																		, m_slotNdx		(slotNdx_)
102 		{}
103 
operator !=(const Iterator<CompatibleType> & other) const104 		bool						operator!=						(const Iterator<CompatibleType>& other) const
105 		{
106 			return m_curBlock != other.m_curBlock || m_slotNdx != other.m_slotNdx;
107 		}
operator ==(const Iterator<CompatibleType> & other) const108 		bool						operator==						(const Iterator<CompatibleType>& other) const
109 		{
110 			return m_curBlock == other.m_curBlock && m_slotNdx == other.m_slotNdx;
111 		}
112 
operator ++(void)113 		Iterator<CompatibleType>&	operator++						(void)
114 		{
115 			++m_slotNdx;
116 
117 			if (m_slotNdx == m_blockSize)
118 			{
119 				m_slotNdx = 0;
120 				m_curBlock = m_curBlock->next;
121 			}
122 
123 			return *this;
124 		}
125 
operator ++(int) const126 		Iterator<CompatibleType>	operator++						(int) const
127 		{
128 			Iterator<CompatibleType> copy(*this);
129 			return ++copy;
130 		}
131 
operator *(void) const132 		CompatibleType&				operator*						(void) const
133 		{
134 			return m_curBlock->elements[m_slotNdx];
135 		}
136 
operator ->(void) const137 		CompatibleType*				operator->						(void) const
138 		{
139 			return &m_curBlock->elements[m_slotNdx];
140 		}
141 
operator Iterator<const CompatibleType>(void) const142 		operator					Iterator<const CompatibleType>	(void) const
143 		{
144 			return Iterator<const CompatibleType>(m_curBlock, m_blockSize, m_slotNdx);
145 		}
146 
147 	private:
148 		Block*			m_curBlock;
149 		size_t			m_blockSize;
150 		size_t			m_slotNdx;
151 	};
152 
153 	typedef Iterator<const ElementType>	const_iterator;
154 	typedef Iterator<ElementType>		iterator;
155 
156 	const_iterator				begin			(void) const;
157 	iterator					begin			(void);
158 
159 	const_iterator				end				(void) const;
160 	iterator					end				(void);
161 };
162 
163 template<typename ElementType>
AppendList(size_t blockSize)164 AppendList<ElementType>::AppendList (size_t blockSize)
165 	: m_blockSize	(blockSize)
166 	, m_numElements	(0)
167 	, m_first		(new Block(0, blockSize))
168 	, m_last		(m_first)
169 {
170 }
171 
172 template<typename ElementType>
~AppendList(void)173 AppendList<ElementType>::~AppendList (void)
174 {
175 	size_t	elementNdx	= 0;
176 	Block*	curBlock	= m_first;
177 
178 	while (curBlock)
179 	{
180 		Block* const	delBlock	= curBlock;
181 
182 		curBlock = delBlock->next;
183 
184 		// Call destructor for allocated elements
185 		for (; elementNdx < min(m_numElements, (delBlock->blockNdx+1)*m_blockSize); ++elementNdx)
186 			delBlock->elements[elementNdx%m_blockSize].~ElementType();
187 
188 		delete delBlock;
189 	}
190 
191 	DE_ASSERT(elementNdx == m_numElements);
192 }
193 
194 template<typename ElementType>
clear(void)195 void AppendList<ElementType>::clear (void)
196 {
197 	// \todo [2016-03-28 pyry] Make thread-safe, if possible
198 
199 	size_t	elementNdx	= 0;
200 	Block*	curBlock	= m_first;
201 
202 	while (curBlock)
203 	{
204 		Block* const	delBlock	= curBlock;
205 
206 		curBlock = delBlock->next;
207 
208 		// Call destructor for allocated elements
209 		for (; elementNdx < min(m_numElements, (delBlock->blockNdx+1)*m_blockSize); ++elementNdx)
210 			delBlock->elements[elementNdx%m_blockSize].~ElementType();
211 
212 		if (delBlock != m_first)
213 			delete delBlock;
214 	}
215 
216 	DE_ASSERT(elementNdx == m_numElements);
217 
218 	m_numElements	= 0;
219 	m_first->next	= DE_NULL;
220 	m_last			= m_first;
221 }
222 
223 template<typename ElementType>
append(const ElementType & value)224 void AppendList<ElementType>::append (const ElementType& value)
225 {
226 	// Fetch curBlock first before allocating slot. Otherwise m_last might get updated before
227 	// this thread gets chance of reading it, leading to curBlock->blockNdx > blockNdx.
228 	Block*			curBlock	= m_last;
229 
230 	deMemoryReadWriteFence();
231 
232 	{
233 		const size_t	elementNdx	= deAtomicIncrementUSize(&m_numElements) - 1;
234 		const size_t	blockNdx	= elementNdx / m_blockSize;
235 		const size_t	slotNdx		= elementNdx - (blockNdx * m_blockSize);
236 
237 		while (curBlock->blockNdx != blockNdx)
238 		{
239 			if (curBlock->next)
240 				curBlock = curBlock->next;
241 			else
242 			{
243 				// Other thread(s) are currently allocating additional block(s)
244 				deYield();
245 			}
246 		}
247 
248 		// Did we allocate last slot? If so, add a new block
249 		if (slotNdx+1 == m_blockSize)
250 		{
251 			Block* const	newBlock	= new Block(blockNdx+1, m_blockSize);
252 
253 			deMemoryReadWriteFence();
254 
255 			// At this point if any other thread is trying to allocate more blocks
256 			// they are being blocked by curBlock->next being null. This guarantees
257 			// that this thread has exclusive modify access to m_last.
258 			m_last = newBlock;
259 			deMemoryReadWriteFence();
260 
261 			// At this point other threads might have skipped to newBlock, but we
262 			// still have exclusive modify access to curBlock->next.
263 			curBlock->next = newBlock;
264 			deMemoryReadWriteFence();
265 		}
266 
267 		new (&curBlock->elements[slotNdx]) ElementType(value);
268 	}
269 }
270 
271 template<typename ElementType>
begin(void) const272 typename AppendList<ElementType>::const_iterator AppendList<ElementType>::begin (void) const
273 {
274 	return const_iterator(m_first, m_blockSize, 0);
275 }
276 
277 template<typename ElementType>
begin(void)278 typename AppendList<ElementType>::iterator AppendList<ElementType>::begin (void)
279 {
280 	return iterator(m_first, m_blockSize, 0);
281 }
282 
283 template<typename ElementType>
end(void) const284 typename AppendList<ElementType>::const_iterator AppendList<ElementType>::end (void) const
285 {
286 	return const_iterator(m_last, m_blockSize, m_numElements%m_blockSize);
287 }
288 
289 template<typename ElementType>
end(void)290 typename AppendList<ElementType>::iterator AppendList<ElementType>::end (void)
291 {
292 	return iterator(m_last, m_blockSize, m_numElements%m_blockSize);
293 }
294 
295 void	AppendList_selfTest		(void);
296 
297 } // de
298 
299 #endif // _DEAPPENDLIST_HPP
300