2
0
Эх сурвалжийг харах

Some work on the BlockArray

Panagiotis Christopoulos Charitos 2 жил өмнө
parent
commit
5d52ea7cf3

+ 22 - 0
AnKi/Util/BitSet.h

@@ -285,6 +285,28 @@ public:
 		return m_chunks;
 	}
 
+	/// Unset the N least significant bits of the bitset.
+	BitSet& unsetNLeastSignificantBits(U32 n)
+	{
+		ANKI_ASSERT(n);
+		U32 high, low;
+		position(n - 1, high, low);
+		for(U32 i = 0; i < high; ++i)
+		{
+			m_chunks[i] = 0;
+		}
+
+		// This could be a simple 1<<(low+1) but that may overflow so...
+		ChunkType mask = (ChunkType(1) << ChunkType(low)) - 1;
+		mask <<= 1;
+		mask |= 1;
+
+		mask = ~mask;
+		m_chunks[high] &= mask;
+
+		return *this;
+	}
+
 private:
 	Array<ChunkType, kChunkCount> m_chunks;
 

+ 67 - 11
AnKi/Util/BlockArray.h

@@ -23,7 +23,7 @@ class BlockArrayDefaultConfig
 public:
 	static constexpr U32 getElementCountPerBlock()
 	{
-		return getAlignedRoundDown(ANKI_CACHE_LINE_SIZE, sizeof(T) * 64) / sizeof(T);
+		return U32(getAlignedRoundDown(ANKI_CACHE_LINE_SIZE, sizeof(T) * 64) / sizeof(T));
 	}
 };
 
@@ -31,6 +31,12 @@ public:
 template<typename TValuePointer, typename TValueReference, typename TBlockArrayPtr>
 class BlockArrayIterator
 {
+	template<typename, typename, typename>
+	friend class BlockArray;
+
+	template<typename, typename, typename>
+	friend class BlockArrayIterator;
+
 public:
 	/// Default constructor.
 	BlockArrayIterator()
@@ -103,7 +109,7 @@ public:
 	BlockArrayIterator& operator++()
 	{
 		check();
-		m_elementIdx = (m_elementIdx != kMaxU32) ? (m_elementIdx + 1) : kMaxU32;
+		m_elementIdx = (m_elementIdx != kMaxU32) ? (m_array->getNextElementIndex(m_elementIdx)) : kMaxU32;
 		return *this;
 	}
 
@@ -142,6 +148,13 @@ public:
 		return !(*this == b);
 	}
 
+	/// Returns the imaginary index inside the BlockArray.
+	U32 getArrayIndex() const
+	{
+		ANKI_ASSERT(m_elementIdx != kMaxU32);
+		return m_elementIdx;
+	}
+
 private:
 	TBlockArrayPtr m_array;
 	U32 m_elementIdx;
@@ -162,6 +175,9 @@ private:
 template<typename T, typename TMemoryPool = SingletonMemoryPoolWrapper<DefaultMemoryPool>, typename TConfig = BlockArrayDefaultConfig<T>>
 class BlockArray
 {
+	template<typename, typename, typename>
+	friend class BlockArrayIterator;
+
 public:
 	// Typedefs
 	using Config = TConfig;
@@ -208,8 +224,6 @@ public:
 		m_blockMetadatas = std::move(b.m_blockMetadatas);
 		m_elementCount = b.m_elementCount;
 		b.m_elementCount = 0;
-		m_firstElement = b.m_firstElement;
-		b.m_firstElement = kMaxU32;
 		return *this;
 	}
 
@@ -219,7 +233,7 @@ public:
 		const U32 localIdx = idx % kElementCountPerBlock;
 		ANKI_ASSERT(blockIdx < m_blockMetadatas.getSize());
 		ANKI_ASSERT(m_blockStorages[blockIdx] != nullptr);
-		ANKI_ASSERT(m_blockMetadatas[blockIdx].m_elementsInUseMask[localIdx].get() == true);
+		ANKI_ASSERT(m_blockMetadatas[blockIdx].m_elementsInUseMask.get(localIdx) == true);
 		return *reinterpret_cast<Value*>(&m_blockStorages[blockIdx]->m_storage[localIdx * sizeof(Value)]);
 	}
 
@@ -232,9 +246,9 @@ public:
 	/// Get begin.
 	Iterator getBegin()
 	{
-		return Iterator(this, m_firstElement
+		return Iterator(this, getFirstElementIndex()
 #if ANKI_EXTRA_CHECKS
-						,
+								  ,
 						m_iteratorVer
 #endif
 		);
@@ -243,9 +257,9 @@ public:
 	/// Get begin.
 	ConstIterator getBegin() const
 	{
-		return ConstIterator(this, m_firstElement
+		return ConstIterator(this, getFirstElementIndex()
 #if ANKI_EXTRA_CHECKS
-							 ,
+									   ,
 							 m_iteratorVer
 #endif
 		);
@@ -330,19 +344,61 @@ private:
 		U8 m_storage[sizeof(T) * kElementCountPerBlock];
 	};
 
+	using Mask = BitSet<kElementCountPerBlock, U64>;
+
 	class BlockMetadata
 	{
 	public:
-		BitSet<kElementCountPerBlock, U64> m_elementsInUseMask{false};
+		Mask m_elementsInUseMask{false};
 	};
 
 	DynamicArray<BlockStorage*, TMemoryPool> m_blockStorages;
 	DynamicArray<BlockMetadata, TMemoryPool> m_blockMetadatas;
 	U32 m_elementCount = 0;
-	U32 m_firstElement = kMaxU32;
 #if ANKI_EXTRA_CHECKS
 	U32 m_iteratorVer = 1;
 #endif
+
+	U32 getFirstElementIndex() const
+	{
+		for(U32 blockIdx = 0; blockIdx < m_blockMetadatas.getSize(); ++blockIdx)
+		{
+			U32 localIdx;
+			if((localIdx = m_blockMetadatas[blockIdx].m_elementsInUseMask.getLeastSignificantBit()) != kMaxU32)
+			{
+				return localIdx + blockIdx * kElementCountPerBlock;
+			}
+		}
+
+		return kMaxU32;
+	}
+
+	U32 getNextElementIndex(U32 crnt) const
+	{
+		ANKI_ASSERT(crnt < kMaxU32);
+		const U32 localIdx = crnt % kElementCountPerBlock;
+		U32 blockIdx = crnt / kElementCountPerBlock;
+		ANKI_ASSERT(blockIdx < m_blockMetadatas.getSize());
+
+		Mask mask = m_blockMetadatas[blockIdx].m_elementsInUseMask;
+		mask.unsetNLeastSignificantBits(localIdx + 1);
+		U32 locIdx;
+		if((locIdx = mask.getLeastSignificantBit()) != kMaxU32)
+		{
+			return blockIdx * kElementCountPerBlock + locIdx;
+		}
+
+		++blockIdx;
+		for(; blockIdx < m_blockMetadatas.getSize(); ++blockIdx)
+		{
+			if((locIdx = m_blockMetadatas[blockIdx].m_elementsInUseMask.getLeastSignificantBit()) != kMaxU32)
+			{
+				return blockIdx * kElementCountPerBlock + locIdx;
+			}
+		}
+
+		return kMaxU32;
+	}
 };
 /// @}
 

+ 139 - 0
AnKi/Util/BlockArray.inl.h

@@ -7,4 +7,143 @@
 
 namespace anki {
 
+template<typename T, typename TMemoryPool, typename TConfig>
+void BlockArray<T, TMemoryPool, TConfig>::destroy()
+{
+	for(U32 i = 0; i < m_blockStorages.getSize(); ++i)
+	{
+		U32 localIdx;
+		while((localIdx = m_blockMetadatas[i].m_elementsInUseMask.getLeastSignificantBit()) != kMaxU32)
+		{
+			reinterpret_cast<T*>(&m_blockStorages[i]->m_storage[localIdx * sizeof(T)])->~T();
+			m_blockMetadatas[i].m_elementsInUseMask.unset(localIdx);
+		}
+
+		if(m_blockStorages[i])
+		{
+			getMemoryPool().free(m_blockStorages[i]);
+		}
+	}
+
+	m_blockMetadatas.destroy();
+	m_blockStorages.destroy();
+	m_elementCount = 0;
+}
+
+template<typename T, typename TMemoryPool, typename TConfig>
+template<typename... TArgs>
+BlockArray<T, TMemoryPool, TConfig>::Iterator BlockArray<T, TMemoryPool, TConfig>::emplace(TArgs&&... args)
+{
+	U32 localIdx = kMaxU32;
+	U32 blockIdx = kMaxU32;
+
+	// Search for a block with free elements
+	for(U32 i = 0; i < m_blockStorages.getSize(); ++i)
+	{
+		if(m_blockMetadatas[i].m_elementsInUseMask.getEnabledBitCount() < kElementCountPerBlock)
+		{
+			// Found a block, allocate from it
+			auto unsetBits = ~m_blockMetadatas[i].m_elementsInUseMask;
+			localIdx = unsetBits.getLeastSignificantBit();
+			blockIdx = i;
+
+			if(m_blockStorages[blockIdx] == nullptr)
+			{
+				m_blockStorages[blockIdx] = newInstance<BlockStorage>(getMemoryPool());
+			}
+
+			break;
+		}
+	}
+
+	// Block not found, crate new
+	if(blockIdx == kMaxU32)
+	{
+		m_blockMetadatas.emplaceBack(false);
+		m_blockStorages.emplaceBack(newInstance<BlockStorage>(getMemoryPool()));
+
+		localIdx = 0;
+		blockIdx = m_blockMetadatas.getSize() - 1;
+	}
+
+	// Finalize and return
+	ANKI_ASSERT(localIdx < kElementCountPerBlock);
+
+	::new(&m_blockStorages[blockIdx]->m_storage[localIdx * sizeof(T)]) T(std::forward<TArgs>(args)...);
+
+	ANKI_ASSERT(m_blockMetadatas[blockIdx].m_elementsInUseMask.get(localIdx) == false);
+	m_blockMetadatas[blockIdx].m_elementsInUseMask.set(localIdx);
+
+#if ANKI_EXTRA_CHECKS
+	++m_iteratorVer;
+#endif
+	++m_elementCount;
+
+	return Iterator(this, blockIdx * kElementCountPerBlock + localIdx
+#if ANKI_EXTRA_CHECKS
+					,
+					m_iteratorVer
+#endif
+	);
+}
+
+template<typename T, typename TMemoryPool, typename TConfig>
+void BlockArray<T, TMemoryPool, TConfig>::erase(ConstIterator at)
+{
+	const U32 idx = at.m_elementIdx;
+	const U32 localIdx = idx % kElementCountPerBlock;
+	const U32 blockIdx = idx / kElementCountPerBlock;
+	ANKI_ASSERT(blockIdx < m_blockStorages.getSize());
+
+	Mask& inUseMask = m_blockMetadatas[blockIdx].m_elementsInUseMask;
+	ANKI_ASSERT(inUseMask.get(localIdx) == true);
+	BlockStorage* block = m_blockStorages[blockIdx];
+	ANKI_ASSERT(block);
+
+	reinterpret_cast<T*>(&block->m_storage[localIdx * sizeof(T)])->~T();
+
+	inUseMask.unset(localIdx);
+	if(inUseMask.getEnabledBitCount() == 0)
+	{
+		// Block is empty, delete it
+		getMemoryPool().free(block);
+		m_blockStorages[blockIdx] = nullptr;
+	}
+
+	ANKI_ASSERT(m_elementCount > 0);
+	--m_elementCount;
+}
+
+template<typename T, typename TMemoryPool, typename TConfig>
+BlockArray<T, TMemoryPool, TConfig>& BlockArray<T, TMemoryPool, TConfig>::operator=(const BlockArray& b)
+{
+	destroy();
+
+	if(b.m_elementCount == 0)
+	{
+		return *this;
+	}
+
+	m_elementCount = b.m_elementCount;
+	m_blockStorages.resize(b.m_blockStorages.getSize());
+	m_blockMetadatas.resize(b.m_blockMetadatas.getSize());
+#if ANKI_EXTRA_CHECKS
+	++m_iteratorVer;
+#endif
+
+	for(U32 blockIdx = 0; blockIdx < b.m_blockMetadatas.getSize(); ++blockIdx)
+	{
+		Mask mask = b.m_blockMetadatas[blockIdx].m_elementsInUseMask;
+		U32 localIdx;
+		while((localIdx = mask.getLeastSignificantBit()) != kMaxU32)
+		{
+			const T& other = b[blockIdx * kElementCountPerBlock + localIdx];
+			::new(&b.m_blockStorages[blockIdx].m_storage[localIdx * sizeof(T)]) T(other);
+			mask.unset(localIdx);
+		}
+	}
+
+	return *this;
+}
+
 } // end namespace anki

+ 77 - 0
Tests/Framework/TestFoo.h

@@ -0,0 +1,77 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <Tests/Framework/Framework.h>
+
+namespace anki {
+
+/// A simple class used for testing containers.
+class TestFoo
+{
+public:
+	int m_x;
+
+	static inline int m_constructorCount = 0;
+	static inline int m_destructorCount = 0;
+	static inline int m_copyCount = 0;
+	static inline int m_moveCount = 0;
+
+	TestFoo()
+		: m_x(0)
+	{
+		++m_constructorCount;
+	}
+
+	TestFoo(int x)
+		: m_x(x)
+	{
+		++m_constructorCount;
+	}
+
+	TestFoo(const TestFoo& b)
+		: m_x(b.m_x)
+	{
+		++m_constructorCount;
+	}
+
+	TestFoo(TestFoo&& b)
+		: m_x(b.m_x)
+	{
+		b.m_x = 0;
+		++m_constructorCount;
+	}
+
+	~TestFoo()
+	{
+		++m_destructorCount;
+	}
+
+	TestFoo& operator=(const TestFoo& b)
+	{
+		m_x = b.m_x;
+		++m_copyCount;
+		return *this;
+	}
+
+	TestFoo& operator=(TestFoo&& b)
+	{
+		m_x = b.m_x;
+		b.m_x = 0;
+		++m_moveCount;
+		return *this;
+	}
+
+	static void reset()
+	{
+		m_constructorCount = 0;
+		m_destructorCount = 0;
+		m_copyCount = 0;
+		m_moveCount = 0;
+	}
+};
+
+} // end namespace anki

+ 22 - 0
Tests/Util/BitSet.cpp

@@ -132,6 +132,23 @@ static void checkLsb()
 	ANKI_TEST_EXPECT_EQ(a.getLeastSignificantBit(), 0);
 }
 
+template<typename TChunkType>
+static void checkSetNLeastSignificant()
+{
+	constexpr U32 kCount = 256;
+	BitSet<kCount, TChunkType> a = {true};
+
+	for(U32 n = 1; n <= kCount; ++n)
+	{
+		a.setAll();
+		a.unsetNLeastSignificantBits(n);
+		for(U i = 0; i < kCount; ++i)
+		{
+			ANKI_TEST_EXPECT_EQ(a.get(i), (i < n) ? false : true);
+		}
+	}
+}
+
 ANKI_TEST(Util, BitSet)
 {
 	{
@@ -159,4 +176,9 @@ ANKI_TEST(Util, BitSet)
 	checkLsb<U16>();
 	checkLsb<U32>();
 	checkLsb<U64>();
+
+	checkSetNLeastSignificant<U8>();
+	checkSetNLeastSignificant<U16>();
+	checkSetNLeastSignificant<U32>();
+	checkSetNLeastSignificant<U64>();
 }

+ 78 - 0
Tests/Util/BlockArray.cpp

@@ -0,0 +1,78 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <Tests/Framework/Framework.h>
+#include <Tests/Framework/TestFoo.h>
+#include <AnKi/Util/BlockArray.h>
+#include <vector>
+
+ANKI_TEST(Util, BlockArray)
+{
+	DefaultMemoryPool::allocateSingleton(allocAligned, nullptr);
+
+	// Simple
+	TestFoo::reset();
+	{
+		BlockArray<TestFoo> arr;
+		auto it = arr.emplace(123);
+		ANKI_TEST_EXPECT_EQ(it->m_x, 123);
+
+		auto it2 = arr.emplace(124);
+		ANKI_TEST_EXPECT_EQ(it2->m_x, 124);
+
+		auto it3 = arr.emplace(666);
+		ANKI_TEST_EXPECT_EQ(it3->m_x, 666);
+
+		arr.erase(arr.getBegin() + 1);
+		ANKI_TEST_EXPECT_EQ(arr.getSize(), 2);
+
+		int sum = 0;
+		for(auto& it : arr)
+		{
+			sum += it.m_x;
+		}
+		ANKI_TEST_EXPECT_EQ(sum, 123 + 666);
+	}
+	ANKI_TEST_EXPECT_EQ(TestFoo::m_constructorCount, TestFoo::m_destructorCount);
+	ANKI_TEST_EXPECT_EQ(TestFoo::m_copyCount, 0);
+
+	// Fuzzy
+	TestFoo::reset();
+	{
+		constexpr U32 kOperations = 1000;
+		std::vector<std::pair<int, int>> vec;
+		BlockArray<TestFoo> arr;
+
+		for(U32 op = 0; op < kOperations; ++op)
+		{
+			const int opType = rand() % 2u;
+
+			if(opType == 0 && vec.size())
+			{
+				// Remove something
+				const int randPos = rand() % vec.size();
+
+				ANKI_TEST_EXPECT_EQ(arr[vec[randPos].first].m_x, vec[randPos].second);
+				arr.erase(arr.getBegin() + vec[randPos].first);
+
+				vec.erase(vec.begin() + randPos);
+			}
+			else
+			{
+				const int randVal = rand();
+				auto it = arr.emplace(randVal);
+				vec.push_back({it.getArrayIndex(), randVal});
+			}
+		}
+
+		ANKI_TEST_EXPECT_EQ(vec.size(), arr.getSize());
+		for(auto it : vec)
+		{
+			ANKI_TEST_EXPECT_EQ(arr[it.first].m_x, it.second);
+		}
+	}
+
+	DefaultMemoryPool::freeSingleton();
+}