Browse Source

SparseArray: Faster than unordered_map and seems bug free

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
8bf4bd55ce
3 changed files with 354 additions and 156 deletions
  1. 116 33
      src/anki/util/SparseArray.h
  2. 78 60
      src/anki/util/SparseArray.inl.h
  3. 160 63
      tests/util/SparseArray.cpp

+ 116 - 33
src/anki/util/SparseArray.h

@@ -29,6 +29,9 @@ public:
 	SparseArrayIterator()
 	SparseArrayIterator()
 		: m_array(nullptr)
 		: m_array(nullptr)
 		, m_elementIdx(MAX_U32)
 		, m_elementIdx(MAX_U32)
+#if ANKI_EXTRA_CHECKS
+		, m_iteratorVer(MAX_U32)
+#endif
 	{
 	{
 	}
 	}
 
 
@@ -36,6 +39,9 @@ public:
 	SparseArrayIterator(const SparseArrayIterator& b)
 	SparseArrayIterator(const SparseArrayIterator& b)
 		: m_array(b.m_array)
 		: m_array(b.m_array)
 		, m_elementIdx(b.m_elementIdx)
 		, m_elementIdx(b.m_elementIdx)
+#if ANKI_EXTRA_CHECKS
+		, m_iteratorVer(b.m_iteratorVer)
+#endif
 	{
 	{
 	}
 	}
 
 
@@ -44,12 +50,24 @@ public:
 	SparseArrayIterator(const SparseArrayIterator<YValuePointer, YValueReference, YSparseArrayPtr>& b)
 	SparseArrayIterator(const SparseArrayIterator<YValuePointer, YValueReference, YSparseArrayPtr>& b)
 		: m_array(b.m_array)
 		: m_array(b.m_array)
 		, m_elementIdx(b.m_elementIdx)
 		, m_elementIdx(b.m_elementIdx)
+#if ANKI_EXTRA_CHECKS
+		, m_iteratorVer(b.m_iteratorVer)
+#endif
 	{
 	{
 	}
 	}
 
 
-	SparseArrayIterator(TSparseArrayPtr arr, U32 modIdx)
+	SparseArrayIterator(TSparseArrayPtr arr,
+		U32 modIdx
+#if ANKI_EXTRA_CHECKS
+		,
+		U32 ver
+#endif
+		)
 		: m_array(arr)
 		: m_array(arr)
 		, m_elementIdx(modIdx)
 		, m_elementIdx(modIdx)
+#if ANKI_EXTRA_CHECKS
+		, m_iteratorVer(ver)
+#endif
 	{
 	{
 		ANKI_ASSERT(arr);
 		ANKI_ASSERT(arr);
 	}
 	}
@@ -57,13 +75,13 @@ public:
 	TValueReference operator*() const
 	TValueReference operator*() const
 	{
 	{
 		check();
 		check();
-		return m_array->m_elements[m_elementIdx].m_value;
+		return m_array->m_elements[m_elementIdx];
 	}
 	}
 
 
 	TValuePointer operator->() const
 	TValuePointer operator->() const
 	{
 	{
 		check();
 		check();
-		return &m_array->m_elements[m_elementIdx].m_value;
+		return &m_array->m_elements[m_elementIdx];
 	}
 	}
 
 
 	SparseArrayIterator& operator++()
 	SparseArrayIterator& operator++()
@@ -98,6 +116,7 @@ public:
 	Bool operator==(const SparseArrayIterator& b) const
 	Bool operator==(const SparseArrayIterator& b) const
 	{
 	{
 		ANKI_ASSERT(m_array == b.m_array);
 		ANKI_ASSERT(m_array == b.m_array);
+		ANKI_ASSERT(m_iteratorVer == b.m_iteratorVer);
 		return m_elementIdx == b.m_elementIdx;
 		return m_elementIdx == b.m_elementIdx;
 	}
 	}
 
 
@@ -106,19 +125,19 @@ public:
 		return !(*this == b);
 		return !(*this == b);
 	}
 	}
 
 
-	operator Bool() const
-	{
-		return m_elementIdx != MAX_U32;
-	}
-
 private:
 private:
 	TSparseArrayPtr m_array;
 	TSparseArrayPtr m_array;
 	U32 m_elementIdx;
 	U32 m_elementIdx;
+#if ANKI_EXTRA_CHECKS
+	U32 m_iteratorVer; ///< See SparseArray::m_iteratorVer.
+#endif
 
 
 	void check() const
 	void check() const
 	{
 	{
-		ANKI_ASSERT(m_elementIdx != MAX_U32 && m_array);
-		ANKI_ASSERT(m_array->m_elements[m_elementIdx].m_alive);
+		ANKI_ASSERT(m_array);
+		ANKI_ASSERT(m_elementIdx != MAX_U32);
+		ANKI_ASSERT(m_array->m_metadata[m_elementIdx].m_alive);
+		ANKI_ASSERT(m_array->m_iteratorVer == m_iteratorVer);
 	}
 	}
 };
 };
 
 
@@ -145,7 +164,7 @@ public:
 	{
 	{
 		ANKI_ASSERT(initialStorageSize > 0 && isPowerOfTwo(initialStorageSize));
 		ANKI_ASSERT(initialStorageSize > 0 && isPowerOfTwo(initialStorageSize));
 		ANKI_ASSERT(probeCount > 0 && probeCount < initialStorageSize);
 		ANKI_ASSERT(probeCount > 0 && probeCount < initialStorageSize);
-		ANKI_ASSERT(maxLoadFactor > 0.0f && maxLoadFactor <= 1.0f);
+		ANKI_ASSERT(maxLoadFactor > 0.5f && maxLoadFactor < 1.0f);
 	}
 	}
 
 
 	/// Constructor #2.
 	/// Constructor #2.
@@ -178,7 +197,7 @@ public:
 	/// Destroy.
 	/// Destroy.
 	~SparseArray()
 	~SparseArray()
 	{
 	{
-		ANKI_ASSERT(m_elements == nullptr && "Forgot to call destroy");
+		ANKI_ASSERT(m_elements == nullptr && m_metadata == nullptr && "Forgot to call destroy");
 	}
 	}
 
 
 	/// Non-copyable.
 	/// Non-copyable.
@@ -187,14 +206,18 @@ public:
 	/// Move operator.
 	/// Move operator.
 	SparseArray& operator=(SparseArray&& b)
 	SparseArray& operator=(SparseArray&& b)
 	{
 	{
-		ANKI_ASSERT(m_elements == nullptr && "Forgot to call destroy");
+		ANKI_ASSERT(m_elements == nullptr && m_metadata == nullptr && "Forgot to call destroy");
 
 
 		m_elements = b.m_elements;
 		m_elements = b.m_elements;
+		m_metadata = b.m_metadata;
 		m_elementCount = b.m_elementCount;
 		m_elementCount = b.m_elementCount;
 		m_capacity = b.m_capacity;
 		m_capacity = b.m_capacity;
 		m_initialStorageSize = b.m_initialStorageSize;
 		m_initialStorageSize = b.m_initialStorageSize;
 		m_probeCount = b.m_probeCount;
 		m_probeCount = b.m_probeCount;
 		m_maxLoadFactor = b.m_maxLoadFactor;
 		m_maxLoadFactor = b.m_maxLoadFactor;
+#if ANKI_EXTRA_CHECKS
+		++m_iteratorVer;
+#endif
 
 
 		b.resetMembers();
 		b.resetMembers();
 
 
@@ -204,25 +227,49 @@ public:
 	/// Get begin.
 	/// Get begin.
 	Iterator getBegin()
 	Iterator getBegin()
 	{
 	{
-		return Iterator(this, findFirstAlive());
+		return Iterator(this,
+			findFirstAlive()
+#if ANKI_EXTRA_CHECKS
+				,
+			m_iteratorVer
+#endif
+			);
 	}
 	}
 
 
 	/// Get begin.
 	/// Get begin.
 	ConstIterator getBegin() const
 	ConstIterator getBegin() const
 	{
 	{
-		return ConstIterator(this, findFirstAlive());
+		return ConstIterator(this,
+			findFirstAlive()
+#if ANKI_EXTRA_CHECKS
+				,
+			m_iteratorVer
+#endif
+			);
 	}
 	}
 
 
 	/// Get end.
 	/// Get end.
 	Iterator getEnd()
 	Iterator getEnd()
 	{
 	{
-		return Iterator(this, MAX_U32);
+		return Iterator(this,
+			MAX_U32
+#if ANKI_EXTRA_CHECKS
+			,
+			m_iteratorVer
+#endif
+			);
 	}
 	}
 
 
 	/// Get end.
 	/// Get end.
 	ConstIterator getEnd() const
 	ConstIterator getEnd() const
 	{
 	{
-		return ConstIterator(this, MAX_U32);
+		return ConstIterator(this,
+			MAX_U32
+#if ANKI_EXTRA_CHECKS
+			,
+			m_iteratorVer
+#endif
+			);
 	}
 	}
 
 
 	/// Get begin.
 	/// Get begin.
@@ -272,13 +319,25 @@ public:
 	/// Get an iterator.
 	/// Get an iterator.
 	Iterator find(Index idx)
 	Iterator find(Index idx)
 	{
 	{
-		return Iterator(this, findInternal(idx));
+		return Iterator(this,
+			findInternal(idx)
+#if ANKI_EXTRA_CHECKS
+				,
+			m_iteratorVer
+#endif
+			);
 	}
 	}
 
 
 	/// Get an iterator.
 	/// Get an iterator.
 	ConstIterator find(Index idx) const
 	ConstIterator find(Index idx) const
 	{
 	{
-		return ConstIterator(this, findInternal(idx));
+		return ConstIterator(this,
+			findInternal(idx)
+#if ANKI_EXTRA_CHECKS
+				,
+			m_iteratorVer
+#endif
+			);
 	}
 	}
 
 
 	/// Remove an element.
 	/// Remove an element.
@@ -289,28 +348,34 @@ public:
 	void validate() const;
 	void validate() const;
 
 
 protected:
 protected:
-	/// Element.
-	class Element
+	/// Element metadata.
+	class Metadata
 	{
 	{
 	public:
 	public:
-		Value m_value;
 		Index m_idx;
 		Index m_idx;
-		Bool8 m_alive;
+		Bool m_alive;
 
 
-		Element() = delete;
-		Element(const Element&) = delete;
-		Element(Element&&) = delete;
-		~Element() = delete;
+		Metadata() = delete;
+		Metadata(const Metadata&) = delete;
+		Metadata(Metadata&&) = delete;
+		~Metadata() = delete;
 	};
 	};
 
 
-	Element* m_elements = nullptr;
+	Value* m_elements = nullptr;
+	Metadata* m_metadata = nullptr;
 	U32 m_elementCount = 0;
 	U32 m_elementCount = 0;
 	U32 m_capacity = 0;
 	U32 m_capacity = 0;
 
 
 	U32 m_initialStorageSize = 0;
 	U32 m_initialStorageSize = 0;
 	U32 m_probeCount = 0;
 	U32 m_probeCount = 0;
 	F32 m_maxLoadFactor = 0.0;
 	F32 m_maxLoadFactor = 0.0;
+#if ANKI_EXTRA_CHECKS
+	/// Iterators version. Used to check if iterators point to the newest storage. Needs to be changed whenever we need
+	/// to invalidate iterators.
+	U32 m_iteratorVer = 0;
+#endif
 
 
+	/// Wrap an index.
 	Index mod(const Index idx) const
 	Index mod(const Index idx) const
 	{
 	{
 		ANKI_ASSERT(m_capacity > 0);
 		ANKI_ASSERT(m_capacity > 0);
@@ -318,6 +383,7 @@ protected:
 		return idx & (m_capacity - 1);
 		return idx & (m_capacity - 1);
 	}
 	}
 
 
+	/// Wrap an index.
 	static Index mod(const Index idx, U32 capacity)
 	static Index mod(const Index idx, U32 capacity)
 	{
 	{
 		ANKI_ASSERT(capacity > 0);
 		ANKI_ASSERT(capacity > 0);
@@ -332,7 +398,7 @@ protected:
 		return F32(m_elementCount) / m_capacity;
 		return F32(m_elementCount) / m_capacity;
 	}
 	}
 
 
-	/// Insert a value.
+	/// Insert a value. This method will move the val to a new place.
 	/// @return One if the idx was a new element or zero if the idx was there already.
 	/// @return One if the idx was a new element or zero if the idx was there already.
 	template<typename TAlloc>
 	template<typename TAlloc>
 	U32 insert(TAlloc& alloc, Index idx, Value& val);
 	U32 insert(TAlloc& alloc, Index idx, Value& val);
@@ -358,7 +424,7 @@ protected:
 
 
 		for(U32 i = 0; i < m_capacity; ++i)
 		for(U32 i = 0; i < m_capacity; ++i)
 		{
 		{
-			if(m_elements[i].m_alive)
+			if(m_metadata[i].m_alive)
 			{
 			{
 				return i;
 				return i;
 			}
 			}
@@ -375,8 +441,10 @@ protected:
 	void resetMembers()
 	void resetMembers()
 	{
 	{
 		m_elements = nullptr;
 		m_elements = nullptr;
+		m_metadata = nullptr;
 		m_elementCount = 0;
 		m_elementCount = 0;
 		m_capacity = 0;
 		m_capacity = 0;
+		invalidateIterators();
 	}
 	}
 
 
 	/// Iterate a number of elements.
 	/// Iterate a number of elements.
@@ -384,16 +452,31 @@ protected:
 	{
 	{
 		ANKI_ASSERT(pos < m_capacity);
 		ANKI_ASSERT(pos < m_capacity);
 		ANKI_ASSERT(n > 0);
 		ANKI_ASSERT(n > 0);
-		ANKI_ASSERT(m_elements[pos].m_alive);
+		ANKI_ASSERT(m_metadata[pos].m_alive);
 
 
 		while(n > 0 && ++pos < m_capacity)
 		while(n > 0 && ++pos < m_capacity)
 		{
 		{
-			ANKI_ASSERT(m_elements[pos].m_alive == 1 || m_elements[pos].m_alive == 0);
-			n -= U32(m_elements[pos].m_alive);
+			ANKI_ASSERT(m_metadata[pos].m_alive == 1 || m_metadata[pos].m_alive == 0);
+			n -= U32(m_metadata[pos].m_alive);
 		}
 		}
 
 
 		return (pos >= m_capacity) ? MAX_U32 : pos;
 		return (pos >= m_capacity) ? MAX_U32 : pos;
 	}
 	}
+
+	void destroyElement(Value& v)
+	{
+		v.~Value();
+#if ANKI_EXTRA_CHECKS
+		memset(&v, 0xC, sizeof(v));
+#endif
+	}
+
+	void invalidateIterators()
+	{
+#if ANKI_EXTRA_CHECKS
+		++m_iteratorVer;
+#endif
+	}
 };
 };
 /// @}
 /// @}
 
 

+ 78 - 60
src/anki/util/SparseArray.inl.h

@@ -16,13 +16,16 @@ void SparseArray<T, TIndex>::destroy(TAlloc& alloc)
 	{
 	{
 		for(Index i = 0; i < m_capacity; ++i)
 		for(Index i = 0; i < m_capacity; ++i)
 		{
 		{
-			if(m_elements[i].m_alive)
+			if(m_metadata[i].m_alive)
 			{
 			{
-				m_elements[i].m_value.~Value();
+				destroyElement(m_elements[i]);
 			}
 			}
 		}
 		}
 
 
 		alloc.deallocate(m_elements, m_capacity);
 		alloc.deallocate(m_elements, m_capacity);
+
+		ANKI_ASSERT(m_metadata);
+		alloc.deallocate(m_metadata, m_capacity);
 	}
 	}
 
 
 	resetMembers();
 	resetMembers();
@@ -39,6 +42,8 @@ void SparseArray<T, TIndex>::emplace(TAlloc& alloc, Index idx, TArgs&&... args)
 
 
 	Value tmp(std::forward<TArgs>(args)...);
 	Value tmp(std::forward<TArgs>(args)...);
 	m_elementCount += insert(alloc, idx, tmp);
 	m_elementCount += insert(alloc, idx, tmp);
+
+	invalidateIterators();
 }
 }
 
 
 template<typename T, typename TIndex>
 template<typename T, typename TIndex>
@@ -46,42 +51,43 @@ template<typename TAlloc>
 U32 SparseArray<T, TIndex>::insert(TAlloc& alloc, Index idx, Value& val)
 U32 SparseArray<T, TIndex>::insert(TAlloc& alloc, Index idx, Value& val)
 {
 {
 start:
 start:
-	Index desiredPos = mod(idx);
-	Index endPos = mod(desiredPos + m_probeCount);
+	const Index desiredPos = mod(idx);
+	const Index endPos = mod(desiredPos + m_probeCount);
 	Index pos = desiredPos;
 	Index pos = desiredPos;
 
 
 	while(pos != endPos)
 	while(pos != endPos)
 	{
 	{
-		Element& el = m_elements[pos];
+		Metadata& meta = m_metadata[pos];
+		Value& crntVal = m_elements[pos];
 
 
-		if(!el.m_alive)
+		if(!meta.m_alive)
 		{
 		{
 			// Empty slot was found, construct in-place
 			// Empty slot was found, construct in-place
 
 
-			el.m_alive = true;
-			el.m_idx = idx;
-			::new(&el.m_value) Value(std::move(val));
+			meta.m_alive = true;
+			meta.m_idx = idx;
+			::new(&crntVal) Value(std::move(val));
 
 
 			return 1;
 			return 1;
 		}
 		}
-		else if(el.m_idx == idx)
+		else if(meta.m_idx == idx)
 		{
 		{
 			// Same index was found, replace
 			// Same index was found, replace
 
 
-			el.m_idx = idx;
-			el.m_value.~Value();
-			::new(&el.m_value) Value(std::move(val));
+			meta.m_idx = idx;
+			destroyElement(crntVal);
+			::new(&crntVal) Value(std::move(val));
 
 
 			return 0;
 			return 0;
 		}
 		}
 
 
 		// Do the robin-hood
 		// Do the robin-hood
-		const Index otherDesiredPos = mod(el.m_idx);
+		const Index otherDesiredPos = mod(meta.m_idx);
 		if(distanceFromDesired(pos, otherDesiredPos) < distanceFromDesired(pos, desiredPos))
 		if(distanceFromDesired(pos, otherDesiredPos) < distanceFromDesired(pos, desiredPos))
 		{
 		{
 			// Swap
 			// Swap
-			std::swap(val, el.m_value);
-			std::swap(idx, el.m_idx);
+			std::swap(val, crntVal);
+			std::swap(idx, meta.m_idx);
 			goto start;
 			goto start;
 		}
 		}
 
 
@@ -104,19 +110,37 @@ void SparseArray<T, TIndex>::grow(TAlloc& alloc)
 	{
 	{
 		ANKI_ASSERT(m_elementCount == 0);
 		ANKI_ASSERT(m_elementCount == 0);
 		m_capacity = m_initialStorageSize;
 		m_capacity = m_initialStorageSize;
-		m_elements =
-			static_cast<Element*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Element), alignof(Element)));
-		memset(m_elements, 0, m_capacity * sizeof(Element));
+		m_elements = static_cast<Value*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Value), alignof(Value)));
+
+		m_metadata =
+			static_cast<Metadata*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Metadata), alignof(Metadata)));
+
+		memset(m_metadata, 0, m_capacity * sizeof(Metadata));
+
 		return;
 		return;
 	}
 	}
 
 
+	// Allocate new storage
+	Value* const oldElements = m_elements;
+	Metadata* const oldMetadata = m_metadata;
+	const U32 oldCapacity = m_capacity;
+	const U32 oldElementCount = m_elementCount;
+	(void)oldElementCount;
+
+	m_capacity *= 2;
+	m_elements = static_cast<Value*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Value), alignof(Value)));
+	m_metadata =
+		static_cast<Metadata*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Metadata), alignof(Metadata)));
+	memset(m_metadata, 0, m_capacity * sizeof(Metadata));
+	m_elementCount = 0;
+
 	// Find from where we start
 	// Find from where we start
 	Index startPos = ~Index(0);
 	Index startPos = ~Index(0);
-	for(U i = 0; i < m_capacity; ++i)
+	for(U i = 0; i < oldCapacity; ++i)
 	{
 	{
-		if(m_elements[i].m_alive)
+		if(oldMetadata[i].m_alive)
 		{
 		{
-			const Index desiredPos = mod(m_elements[i].m_idx);
+			const Index desiredPos = mod(oldMetadata[i].m_idx, oldCapacity);
 			if(desiredPos <= i)
 			if(desiredPos <= i)
 			{
 			{
 				startPos = i;
 				startPos = i;
@@ -126,28 +150,19 @@ void SparseArray<T, TIndex>::grow(TAlloc& alloc)
 	}
 	}
 	ANKI_ASSERT(startPos != ~Index(0));
 	ANKI_ASSERT(startPos != ~Index(0));
 
 
-	// Allocate new storage
-	Element* oldElements = m_elements;
-	const U32 oldCapacity = m_capacity;
-	const U32 oldElementCount = m_elementCount;
-	(void)oldElementCount;
-
-	m_capacity *= 2;
-	m_elements = static_cast<Element*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Element), alignof(Element)));
-	memset(m_elements, 0, m_capacity * sizeof(Element));
-	m_elementCount = 0;
-
 	// Start re-inserting
 	// Start re-inserting
 	U count = oldCapacity;
 	U count = oldCapacity;
 	Index pos = startPos;
 	Index pos = startPos;
 	while(count--)
 	while(count--)
 	{
 	{
-		if(oldElements[pos].m_alive)
+		if(oldMetadata[pos].m_alive)
 		{
 		{
-			Element& el = oldElements[pos];
-			U32 c = insert(alloc, el.m_idx, el.m_value);
+			U32 c = insert(alloc, oldMetadata[pos].m_idx, oldElements[pos]);
 			ANKI_ASSERT(c > 0);
 			ANKI_ASSERT(c > 0);
 			m_elementCount += c;
 			m_elementCount += c;
+
+			// The element was moved to a new storage so we need to destroy the original
+			destroyElement(oldElements[pos]);
 		}
 		}
 
 
 		pos = mod(pos + 1, oldCapacity);
 		pos = mod(pos + 1, oldCapacity);
@@ -157,6 +172,7 @@ void SparseArray<T, TIndex>::grow(TAlloc& alloc)
 
 
 	// Finalize
 	// Finalize
 	alloc.getMemoryPool().free(oldElements);
 	alloc.getMemoryPool().free(oldElements);
+	alloc.getMemoryPool().free(oldMetadata);
 }
 }
 
 
 template<typename T, typename TIndex>
 template<typename T, typename TIndex>
@@ -165,35 +181,33 @@ void SparseArray<T, TIndex>::erase(TAlloc& alloc, Iterator it)
 {
 {
 	ANKI_ASSERT(it.m_array == this);
 	ANKI_ASSERT(it.m_array == this);
 	ANKI_ASSERT(it.m_elementIdx != MAX_U32);
 	ANKI_ASSERT(it.m_elementIdx != MAX_U32);
+	ANKI_ASSERT(it.m_iteratorVer == m_iteratorVer);
 	ANKI_ASSERT(m_elementCount > 0);
 	ANKI_ASSERT(m_elementCount > 0);
 
 
-	(void)alloc;
-
 	const Index pos = it.m_elementIdx;
 	const Index pos = it.m_elementIdx;
 	ANKI_ASSERT(pos < m_capacity);
 	ANKI_ASSERT(pos < m_capacity);
-	ANKI_ASSERT(m_elements[pos].m_alive);
-
-	// Delete the element in the given pos
-	m_elements[pos].m_value.~Value();
-	m_elements[pos].m_alive = false;
-	--m_elementCount;
+	ANKI_ASSERT(m_metadata[pos].m_alive);
 
 
 	// Shift elements
 	// Shift elements
+	Index crntPos; // Also the one that will get deleted
 	Index nextPos = pos;
 	Index nextPos = pos;
 	while(true)
 	while(true)
 	{
 	{
-		const Index crntPos = nextPos;
+		crntPos = nextPos;
 		nextPos = mod(nextPos + 1);
 		nextPos = mod(nextPos + 1);
 
 
-		Element& nextEl = m_elements[nextPos];
+		Metadata& crntMeta = m_metadata[crntPos];
+		Metadata& nextMeta = m_metadata[nextPos];
+		Value& crntEl = m_elements[crntPos];
+		Value& nextEl = m_elements[nextPos];
 
 
-		if(!nextEl.m_alive)
+		if(!nextMeta.m_alive)
 		{
 		{
 			// On gaps, stop
 			// On gaps, stop
 			break;
 			break;
 		}
 		}
 
 
-		const Index nextDesiredPos = mod(nextEl.m_idx);
+		const Index nextDesiredPos = mod(nextMeta.m_idx);
 		if(nextDesiredPos == nextPos)
 		if(nextDesiredPos == nextPos)
 		{
 		{
 			// The element is where it want's to be, stop
 			// The element is where it want's to be, stop
@@ -201,18 +215,22 @@ void SparseArray<T, TIndex>::erase(TAlloc& alloc, Iterator it)
 		}
 		}
 
 
 		// Shift left
 		// Shift left
-		Element& crntEl = m_elements[crntPos];
-		crntEl.m_value = std::move(nextEl.m_value);
-		crntEl.m_idx = nextEl.m_value;
-		crntEl.m_alive = true;
-		nextEl.m_alive = false;
+		std::swap(crntEl, nextEl);
+		crntMeta.m_idx = nextMeta.m_idx;
 	}
 	}
 
 
+	// Delete the element in the given pos
+	destroyElement(m_elements[crntPos]);
+	m_metadata[crntPos].m_alive = false;
+	--m_elementCount;
+
 	// If you erased everything destroy the storage
 	// If you erased everything destroy the storage
 	if(m_elementCount == 0)
 	if(m_elementCount == 0)
 	{
 	{
 		destroy(alloc);
 		destroy(alloc);
 	}
 	}
+
+	invalidateIterators();
 }
 }
 
 
 template<typename T, typename TIndex>
 template<typename T, typename TIndex>
@@ -220,7 +238,7 @@ void SparseArray<T, TIndex>::validate() const
 {
 {
 	if(m_capacity == 0)
 	if(m_capacity == 0)
 	{
 	{
-		ANKI_ASSERT(m_elementCount == 0 && m_elements == 0);
+		ANKI_ASSERT(m_elementCount == 0 && m_elements == nullptr && m_metadata == nullptr);
 		return;
 		return;
 	}
 	}
 
 
@@ -230,9 +248,9 @@ void SparseArray<T, TIndex>::validate() const
 	Index startPos = ~Index(0);
 	Index startPos = ~Index(0);
 	for(U i = 0; i < m_capacity; ++i)
 	for(U i = 0; i < m_capacity; ++i)
 	{
 	{
-		if(m_elements[i].m_alive)
+		if(m_metadata[i].m_alive)
 		{
 		{
-			const Index desiredPos = mod(m_elements[i].m_idx);
+			const Index desiredPos = mod(m_metadata[i].m_idx);
 			if(desiredPos <= i)
 			if(desiredPos <= i)
 			{
 			{
 				startPos = i;
 				startPos = i;
@@ -248,15 +266,15 @@ void SparseArray<T, TIndex>::validate() const
 	Index prevPos = ~Index(0);
 	Index prevPos = ~Index(0);
 	while(count--)
 	while(count--)
 	{
 	{
-		if(m_elements[pos].m_alive)
+		if(m_metadata[pos].m_alive)
 		{
 		{
-			const Index myDesiredPos = mod(m_elements[pos].m_idx);
+			const Index myDesiredPos = mod(m_metadata[pos].m_idx);
 			const Index myDistanceFromDesired = distanceFromDesired(pos, myDesiredPos);
 			const Index myDistanceFromDesired = distanceFromDesired(pos, myDesiredPos);
 			ANKI_ASSERT(myDistanceFromDesired < m_probeCount);
 			ANKI_ASSERT(myDistanceFromDesired < m_probeCount);
 
 
 			if(prevPos != ~Index(0))
 			if(prevPos != ~Index(0))
 			{
 			{
-				Index prevDesiredPos = mod(m_elements[prevPos].m_idx);
+				Index prevDesiredPos = mod(m_metadata[prevPos].m_idx);
 				ANKI_ASSERT(myDesiredPos >= prevDesiredPos);
 				ANKI_ASSERT(myDesiredPos >= prevDesiredPos);
 			}
 			}
 
 
@@ -287,7 +305,7 @@ TIndex SparseArray<T, TIndex>::findInternal(Index idx) const
 	Index pos = desiredPos;
 	Index pos = desiredPos;
 	while(pos != endPos)
 	while(pos != endPos)
 	{
 	{
-		if(m_elements[pos].m_alive && m_elements[pos].m_idx == idx)
+		if(m_metadata[pos].m_alive && m_metadata[pos].m_idx == idx)
 		{
 		{
 			return pos;
 			return pos;
 		}
 		}

+ 160 - 63
tests/util/SparseArray.cpp

@@ -8,6 +8,89 @@
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HighRezTimer.h>
 #include <unordered_map>
 #include <unordered_map>
 #include <ctime>
 #include <ctime>
+#include <algorithm>
+#include <malloc.h>
+
+namespace anki
+{
+namespace
+{
+
+static I64 constructor0Count = 0;
+static I64 constructor1Count = 0;
+static I64 constructor2Count = 0;
+static I64 constructor3Count = 0;
+static I64 destructorCount = 0;
+static I64 copyCount = 0;
+static I64 moveCount = 0;
+
+class SAFoo
+{
+public:
+	int m_x;
+
+	SAFoo()
+		: m_x(0)
+	{
+		++constructor0Count;
+	}
+
+	SAFoo(int x)
+		: m_x(x)
+	{
+		++constructor1Count;
+	}
+
+	SAFoo(const SAFoo& b)
+		: m_x(b.m_x)
+	{
+		++constructor2Count;
+	}
+
+	SAFoo(SAFoo&& b)
+		: m_x(b.m_x)
+	{
+		b.m_x = 0;
+		++constructor3Count;
+	}
+
+	~SAFoo()
+	{
+		++destructorCount;
+	}
+
+	SAFoo& operator=(const SAFoo& b)
+	{
+		m_x = b.m_x;
+		++copyCount;
+		return *this;
+	}
+
+	SAFoo& operator=(SAFoo&& b)
+	{
+		m_x = b.m_x;
+		b.m_x = 0;
+		++moveCount;
+		return *this;
+	}
+
+	static void checkCalls()
+	{
+		ANKI_TEST_EXPECT_EQ(constructor0Count, 0); // default
+		ANKI_TEST_EXPECT_GT(constructor1Count, 0);
+		ANKI_TEST_EXPECT_EQ(constructor2Count, 0); // copy
+		ANKI_TEST_EXPECT_GT(constructor3Count, 0); // move
+		ANKI_TEST_EXPECT_EQ(destructorCount, constructor1Count + constructor3Count);
+
+		ANKI_TEST_EXPECT_EQ(copyCount, 0); // copy
+		ANKI_TEST_EXPECT_GEQ(moveCount, 0); // move
+
+		constructor0Count = constructor1Count = constructor2Count = constructor3Count = destructorCount = copyCount =
+			moveCount = 0;
+	}
+};
+}
+}
 
 
 ANKI_TEST(Util, SparseArray)
 ANKI_TEST(Util, SparseArray)
 {
 {
@@ -24,24 +107,25 @@ ANKI_TEST(Util, SparseArray)
 		arr.erase(alloc, it);
 		arr.erase(alloc, it);
 	}
 	}
 
 
-	// Check destroy
+	// Check destroy and grow
 	{
 	{
-		SparseArray<PtrSize> arr(64, 2);
+		SparseArray<SAFoo> arr(64, 2);
 
 
 		arr.emplace(alloc, 64 * 1, 123);
 		arr.emplace(alloc, 64 * 1, 123);
 		arr.emplace(alloc, 64 * 2, 124);
 		arr.emplace(alloc, 64 * 2, 124);
 		arr.emplace(alloc, 64 * 3, 125);
 		arr.emplace(alloc, 64 * 3, 125);
 
 
-		ANKI_TEST_EXPECT_EQ(*arr.find(64 * 1), 123);
-		ANKI_TEST_EXPECT_EQ(*arr.find(64 * 2), 124);
-		ANKI_TEST_EXPECT_EQ(*arr.find(64 * 3), 125);
+		ANKI_TEST_EXPECT_EQ(arr.find(64 * 1)->m_x, 123);
+		ANKI_TEST_EXPECT_EQ(arr.find(64 * 2)->m_x, 124);
+		ANKI_TEST_EXPECT_EQ(arr.find(64 * 3)->m_x, 125);
 
 
 		arr.destroy(alloc);
 		arr.destroy(alloc);
+		SAFoo::checkCalls();
 	}
 	}
 
 
 	// Do complex insertions
 	// Do complex insertions
 	{
 	{
-		SparseArray<PtrSize, U32> arr(64, 3);
+		SparseArray<SAFoo, U32> arr(64, 3);
 
 
 		arr.emplace(alloc, 64 * 0 - 1, 1);
 		arr.emplace(alloc, 64 * 0 - 1, 1);
 		// Linear probing to 0
 		// Linear probing to 0
@@ -56,12 +140,13 @@ ANKI_TEST(Util, SparseArray)
 		ANKI_TEST_EXPECT_EQ(arr.getSize(), 5);
 		ANKI_TEST_EXPECT_EQ(arr.getSize(), 5);
 
 
 		arr.destroy(alloc);
 		arr.destroy(alloc);
+		SAFoo::checkCalls();
 	}
 	}
 
 
 	// Fuzzy test
 	// Fuzzy test
 	{
 	{
-		const U MAX = 1000;
-		SparseArray<int, U32> arr;
+		const U MAX = 10000;
+		SparseArray<SAFoo, U32> arr;
 		std::vector<int> numbers;
 		std::vector<int> numbers;
 
 
 		srand(time(nullptr));
 		srand(time(nullptr));
@@ -105,7 +190,7 @@ ANKI_TEST(Util, SparseArray)
 
 
 			auto it = arr.find(num);
 			auto it = arr.find(num);
 			ANKI_TEST_EXPECT_NEQ(it, arr.getEnd());
 			ANKI_TEST_EXPECT_NEQ(it, arr.getEnd());
-			ANKI_TEST_EXPECT_EQ(*it, num);
+			ANKI_TEST_EXPECT_EQ(it->m_x, num);
 			arr.erase(alloc, it);
 			arr.erase(alloc, it);
 
 
 			arr.validate();
 			arr.validate();
@@ -115,7 +200,7 @@ ANKI_TEST(Util, SparseArray)
 	// Fuzzy test #2: Do random insertions and removals
 	// Fuzzy test #2: Do random insertions and removals
 	{
 	{
 		const U MAX = 10000;
 		const U MAX = 10000;
-		SparseArray<int, U32> arr;
+		SparseArray<SAFoo, U32> arr;
 		using StlMap =
 		using StlMap =
 			std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<int, int>>>;
 			std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<int, int>>>;
 		StlMap map(10, std::hash<int>(), std::equal_to<int>(), alloc);
 		StlMap map(10, std::hash<int>(), std::equal_to<int>(), alloc);
@@ -133,8 +218,8 @@ ANKI_TEST(Util, SparseArray)
 					continue;
 					continue;
 				}
 				}
 
 
-				arr.emplace(alloc, idx, idx);
-				map[idx] = idx;
+				arr.emplace(alloc, idx, idx + 1);
+				map[idx] = idx + 1;
 
 
 				arr.validate();
 				arr.validate();
 			}
 			}
@@ -143,8 +228,9 @@ ANKI_TEST(Util, SparseArray)
 				const U idx = U(rand()) % map.size();
 				const U idx = U(rand()) % map.size();
 				auto it = std::next(std::begin(map), idx);
 				auto it = std::next(std::begin(map), idx);
 
 
-				auto it2 = arr.find(it->second);
+				auto it2 = arr.find(it->first);
 				ANKI_TEST_EXPECT_NEQ(it2, arr.getEnd());
 				ANKI_TEST_EXPECT_NEQ(it2, arr.getEnd());
+				ANKI_TEST_EXPECT_EQ(it->second, it2->m_x);
 
 
 				map.erase(it);
 				map.erase(it);
 				arr.erase(alloc, it2);
 				arr.erase(alloc, it2);
@@ -159,9 +245,9 @@ ANKI_TEST(Util, SparseArray)
 				auto it = arr.getBegin();
 				auto it = arr.getBegin();
 				while(it != arr.getEnd())
 				while(it != arr.getEnd())
 				{
 				{
-					I val = *it;
+					I key = it->m_x - 1;
 
 
-					auto it2 = bMap.find(val);
+					auto it2 = bMap.find(key);
 					ANKI_TEST_EXPECT_NEQ(it2, bMap.end());
 					ANKI_TEST_EXPECT_NEQ(it2, bMap.end());
 
 
 					bMap.erase(it2);
 					bMap.erase(it2);
@@ -172,29 +258,48 @@ ANKI_TEST(Util, SparseArray)
 		}
 		}
 
 
 		arr.destroy(alloc);
 		arr.destroy(alloc);
+
+		// Check what the SparseArray have called
+		SAFoo::checkCalls();
 	}
 	}
 }
 }
 
 
-static PtrSize akAllocSize = 0;
+static I64 akAllocSize = 0;
+static I64 akMaxAllocSize = 0;
 static ANKI_DONT_INLINE void* allocAlignedAk(void* userData, void* ptr, PtrSize size, PtrSize alignment)
 static ANKI_DONT_INLINE void* allocAlignedAk(void* userData, void* ptr, PtrSize size, PtrSize alignment)
 {
 {
 	if(ptr == nullptr)
 	if(ptr == nullptr)
 	{
 	{
 		akAllocSize += size;
 		akAllocSize += size;
+		akMaxAllocSize = max(akMaxAllocSize, akAllocSize);
+		return malloc(size);
+	}
+	else
+	{
+		PtrSize s = malloc_usable_size(ptr);
+		akAllocSize -= s;
+		free(ptr);
+		return nullptr;
 	}
 	}
-
-	return allocAligned(userData, ptr, size, alignment);
 }
 }
 
 
-static PtrSize stlAllocSize = 0;
+static I64 stlAllocSize = 0;
+static I64 stlMaxAllocSize = 0;
 static ANKI_DONT_INLINE void* allocAlignedStl(void* userData, void* ptr, PtrSize size, PtrSize alignment)
 static ANKI_DONT_INLINE void* allocAlignedStl(void* userData, void* ptr, PtrSize size, PtrSize alignment)
 {
 {
 	if(ptr == nullptr)
 	if(ptr == nullptr)
 	{
 	{
 		stlAllocSize += size;
 		stlAllocSize += size;
+		stlMaxAllocSize = max(stlMaxAllocSize, stlAllocSize);
+		return malloc(size);
+	}
+	else
+	{
+		PtrSize s = malloc_usable_size(ptr);
+		stlAllocSize -= s;
+		free(ptr);
+		return nullptr;
 	}
 	}
-
-	return allocAligned(userData, ptr, size, alignment);
 }
 }
 
 
 ANKI_TEST(Util, SparseArrayBench)
 ANKI_TEST(Util, SparseArrayBench)
@@ -207,18 +312,17 @@ ANKI_TEST(Util, SparseArrayBench)
 	StlMap stdMap(10, std::hash<int>(), std::equal_to<int>(), allocStl);
 	StlMap stdMap(10, std::hash<int>(), std::equal_to<int>(), allocStl);
 
 
 	using AkMap = SparseArray<int, U32>;
 	using AkMap = SparseArray<int, U32>;
-	AkMap akMap(512, log2(512), 0.9f);
+	AkMap akMap(256, log2(256), 0.90f);
 
 
 	HighRezTimer timer;
 	HighRezTimer timer;
 
 
-	const U COUNT = 1024 * 1024 * 5;
+	const U COUNT = 1024 * 1024 * 6;
 
 
 	// Create a huge set
 	// Create a huge set
-	DynamicArrayAuto<int> vals(allocTml);
+	std::vector<int> vals;
 
 
 	{
 	{
 		std::unordered_map<int, int> tmpMap;
 		std::unordered_map<int, int> tmpMap;
-		vals.create(COUNT);
 
 
 		for(U i = 0; i < COUNT; ++i)
 		for(U i = 0; i < COUNT; ++i)
 		{
 		{
@@ -227,10 +331,10 @@ ANKI_TEST(Util, SparseArrayBench)
 			do
 			do
 			{
 			{
 				v = rand();
 				v = rand();
-			} while(tmpMap.find(v) != tmpMap.end());
+			} while(tmpMap.find(v) != tmpMap.end() && v != 0);
 			tmpMap[v] = 1;
 			tmpMap[v] = 1;
 
 
-			vals[i] = v;
+			vals.push_back(v);
 		}
 		}
 	}
 	}
 
 
@@ -259,6 +363,9 @@ ANKI_TEST(Util, SparseArrayBench)
 
 
 	// Search
 	// Search
 	{
 	{
+		// Search in random order
+		std::random_shuffle(vals.begin(), vals.end());
+
 		int count = 0;
 		int count = 0;
 
 
 		// Find values AnKi
 		// Find values AnKi
@@ -280,56 +387,46 @@ ANKI_TEST(Util, SparseArrayBench)
 		timer.stop();
 		timer.stop();
 		HighRezTimer::Scalar stlTime = timer.getElapsedTime();
 		HighRezTimer::Scalar stlTime = timer.getElapsedTime();
 
 
-		ANKI_TEST_LOGI("Find bench: STL %f AnKi %f | %f%%", stlTime, akTime, stlTime / akTime * 100.0);
+		// Print the "count" so that the compiler won't optimize it
+		ANKI_TEST_LOGI("Find bench: STL %f AnKi %f | %f%% (r:%d)", stlTime, akTime, stlTime / akTime * 100.0, count);
 	}
 	}
 
 
 	// Mem usage
 	// Mem usage
-	const PtrSize stlMemUsage = stlAllocSize + sizeof(stdMap);
-	const PtrSize akMemUsage = akAllocSize + sizeof(akMap);
-	ANKI_TEST_LOGI(
-		"Mem usage: STL %llu AnKi %llu | %f%%", stlMemUsage, akMemUsage, F64(stlMemUsage) / akMemUsage * 100.0);
+	const I64 stlMemUsage = stlMaxAllocSize + sizeof(stdMap);
+	const I64 akMemUsage = akMaxAllocSize + sizeof(akMap);
+	ANKI_TEST_LOGI("Max mem usage: STL %lli AnKi %lli | %f%% (At any given time what was the max mem usage)",
+		stlMemUsage,
+		akMemUsage,
+		F64(stlMemUsage) / akMemUsage * 100.0);
 
 
 	// Deletes
 	// Deletes
 	{
 	{
-		const U DEL_COUNT = COUNT / 2;
-		DynamicArrayAuto<AkMap::Iterator> delValsAk(allocTml);
-		delValsAk.create(DEL_COUNT);
-
-		DynamicArrayAuto<StlMap::iterator> delValsStl(allocTml);
-		delValsStl.create(DEL_COUNT);
-
-		std::unordered_map<int, int> tmpMap;
-		for(U i = 0; i < DEL_COUNT; ++i)
-		{
-			// Put unique keys
-			int v;
-			do
-			{
-				v = vals[rand() % COUNT];
-			} while(tmpMap.find(v) != tmpMap.end());
-			tmpMap[v] = 1;
-
-			delValsAk[i] = akMap.find(v);
-			delValsStl[i] = stdMap.find(v);
-		}
+		// Remove in random order
+		std::random_shuffle(vals.begin(), vals.end());
 
 
 		// Random delete AnKi
 		// Random delete AnKi
-		timer.start();
-		for(U i = 0; i < DEL_COUNT; ++i)
+		HighRezTimer::Scalar akTime = 0.0;
+		for(U i = 0; i < vals.size(); ++i)
 		{
 		{
-			akMap.erase(allocAk, delValsAk[i]);
+			auto it = akMap.find(vals[i]);
+
+			timer.start();
+			akMap.erase(allocAk, it);
+			timer.stop();
+			akTime += timer.getElapsedTime();
 		}
 		}
-		timer.stop();
-		HighRezTimer::Scalar akTime = timer.getElapsedTime();
 
 
 		// Random delete STL
 		// Random delete STL
-		timer.start();
-		for(U i = 0; i < DEL_COUNT; ++i)
+		HighRezTimer::Scalar stlTime = 0.0;
+		for(U i = 0; i < vals.size(); ++i)
 		{
 		{
-			stdMap.erase(delValsStl[i]);
+			auto it = stdMap.find(vals[i]);
+
+			timer.start();
+			stdMap.erase(it);
+			timer.stop();
+			stlTime += timer.getElapsedTime();
 		}
 		}
-		timer.stop();
-		HighRezTimer::Scalar stlTime = timer.getElapsedTime();
 
 
 		ANKI_TEST_LOGI("Deleting bench: STL %f AnKi %f | %f%%\n", stlTime, akTime, stlTime / akTime * 100.0);
 		ANKI_TEST_LOGI("Deleting bench: STL %f AnKi %f | %f%%\n", stlTime, akTime, stlTime / akTime * 100.0);
 	}
 	}