Changeset - r28778:c42d642d099c
[Not reviewed]
master
0 2 0
Peter Nelson - 10 months ago 2024-02-17 18:29:21
peter1138@openttd.org
Change: Improve performance of finding free pool slots. (#12055)

Add a bitmap of used pool slots which allows finding a free pool slot without having to check if each index is already used or not.

Loosely based on a JGRPP patch.
2 files changed with 30 insertions and 14 deletions:
0 comments (0 inline, 0 general)
src/core/pool_func.hpp
Show inline comments
 
@@ -2,24 +2,25 @@
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file pool_func.hpp Some methods of Pool are placed here in order to reduce compilation time and binary size. */
 

	
 
#ifndef POOL_FUNC_HPP
 
#define POOL_FUNC_HPP
 

	
 
#include "alloc_func.hpp"
 
#include "bitmath_func.hpp"
 
#include "mem_func.hpp"
 
#include "pool_type.hpp"
 
#include "../error_func.h"
 

	
 
#include "../saveload/saveload_error.hpp" // SlErrorCorruptFmt
 

	
 
/**
 
 * Helper for defining the method's signature.
 
 * @param type The return type of the method.
 
 */
 
#define DEFINE_POOL_METHOD(type) \
 
	template <class Titem, typename Tindex, size_t Tgrowth_step, size_t Tmax_size, PoolType Tpool_type, bool Tcache, bool Tzero> \
 
@@ -51,52 +52,57 @@ DEFINE_POOL_METHOD(inline)::Pool(const c
 
 * @pre index < Tmax_size
 
 */
 
DEFINE_POOL_METHOD(inline void)::ResizeFor(size_t index)
 
{
 
	assert(index >= this->size);
 
	assert(index < Tmax_size);
 

	
 
	size_t new_size = std::min(Tmax_size, Align(index + 1, Tgrowth_step));
 

	
 
	this->data = ReallocT(this->data, new_size);
 
	MemSetT(this->data + this->size, 0, new_size - this->size);
 

	
 
	this->used_bitmap.resize(Align(new_size, BITMAP_SIZE) / BITMAP_SIZE);
 
	if (this->size % BITMAP_SIZE != 0) {
 
		/* Already-allocated bits above old size are now unused. */
 
		this->used_bitmap[this->size / BITMAP_SIZE] &= ~((~static_cast<BitmapStorage>(0)) << (this->size % BITMAP_SIZE));
 
	}
 
	if (new_size % BITMAP_SIZE != 0) {
 
		/* Bits above new size are considered used. */
 
		this->used_bitmap[new_size / BITMAP_SIZE] |= (~static_cast<BitmapStorage>(0)) << (new_size % BITMAP_SIZE);
 
	}
 

	
 
	this->size = new_size;
 
}
 

	
 
/**
 
 * Searches for first free index
 
 * @return first free index, NO_FREE_ITEM on failure
 
 */
 
DEFINE_POOL_METHOD(inline size_t)::FindFirstFree()
 
{
 
	size_t index = this->first_free;
 

	
 
	for (; index < this->first_unused; index++) {
 
		if (this->data[index] == nullptr) return index;
 
	}
 

	
 
	if (index < this->size) {
 
		return index;
 
	for (auto it = std::next(std::begin(this->used_bitmap), this->first_free / BITMAP_SIZE); it != std::end(this->used_bitmap); ++it) {
 
		BitmapStorage available = ~(*it);
 
		if (available == 0) continue;
 
		return std::distance(std::begin(this->used_bitmap), it) * BITMAP_SIZE + FindFirstBit(available);
 
	}
 

	
 
	assert(index == this->size);
 
	assert(this->first_unused == this->size);
 

	
 
	if (index < Tmax_size) {
 
		this->ResizeFor(index);
 
		return index;
 
	if (this->first_unused < Tmax_size) {
 
		this->ResizeFor(this->first_unused);
 
		return this->first_unused;
 
	}
 

	
 
	assert(this->items == Tmax_size);
 
	assert(this->first_unused == Tmax_size);
 

	
 
	return NO_FREE_ITEM;
 
}
 

	
 
/**
 
 * Makes given index valid
 
 * @param size size of item
 
 * @param index index of item
 
 * @pre index < this->size
 
 * @pre this->Get(index) == nullptr
 
 */
 
DEFINE_POOL_METHOD(inline void *)::AllocateItem(size_t size, size_t index)
 
@@ -113,24 +119,25 @@ DEFINE_POOL_METHOD(inline void *)::Alloc
 
		this->alloc_cache = this->alloc_cache->next;
 
		if (Tzero) {
 
			/* Explicitly casting to (void *) prevents a clang warning -
 
			 * we are actually memsetting a (not-yet-constructed) object */
 
			memset((void *)item, 0, sizeof(Titem));
 
		}
 
	} else if (Tzero) {
 
		item = (Titem *)CallocT<byte>(size);
 
	} else {
 
		item = (Titem *)MallocT<byte>(size);
 
	}
 
	this->data[index] = item;
 
	SetBit(this->used_bitmap[index / BITMAP_SIZE], index % BITMAP_SIZE);
 
	item->index = (Tindex)(uint)index;
 
	return item;
 
}
 

	
 
/**
 
 * Allocates new item
 
 * @param size size of item
 
 * @return pointer to allocated item
 
 * @note FatalError() on failure! (no free item)
 
 */
 
DEFINE_POOL_METHOD(void *)::GetNew(size_t size)
 
{
 
@@ -181,36 +188,41 @@ DEFINE_POOL_METHOD(void)::FreeItem(size_
 
	assert(index < this->size);
 
	assert(this->data[index] != nullptr);
 
	if (Tcache) {
 
		AllocCache *ac = (AllocCache *)this->data[index];
 
		ac->next = this->alloc_cache;
 
		this->alloc_cache = ac;
 
	} else {
 
		free(this->data[index]);
 
	}
 
	this->data[index] = nullptr;
 
	this->first_free = std::min(this->first_free, index);
 
	this->items--;
 
	if (!this->cleaning) Titem::PostDestructor(index);
 
	if (!this->cleaning) {
 
		ClrBit(this->used_bitmap[index / BITMAP_SIZE], index % BITMAP_SIZE);
 
		Titem::PostDestructor(index);
 
	}
 
}
 

	
 
/** Destroys all items in the pool and resets all member variables. */
 
DEFINE_POOL_METHOD(void)::CleanPool()
 
{
 
	this->cleaning = true;
 
	for (size_t i = 0; i < this->first_unused; i++) {
 
		delete this->Get(i); // 'delete nullptr;' is very valid
 
	}
 
	assert(this->items == 0);
 
	free(this->data);
 
	this->used_bitmap.clear();
 
	this->used_bitmap.shrink_to_fit();
 
	this->first_unused = this->first_free = this->size = 0;
 
	this->data = nullptr;
 
	this->cleaning = false;
 

	
 
	if (Tcache) {
 
		while (this->alloc_cache != nullptr) {
 
			AllocCache *ac = this->alloc_cache;
 
			this->alloc_cache = ac->next;
 
			free(ac);
 
		}
 
	}
 
}
src/core/pool_type.hpp
Show inline comments
 
@@ -74,36 +74,40 @@ private:
 
 * @tparam Tpool_type   Type of this pool
 
 * @tparam Tcache       Whether to perform 'alloc' caching, i.e. don't actually free/malloc just reuse the memory
 
 * @tparam Tzero        Whether to zero the memory
 
 * @warning when Tcache is enabled *all* instances of this pool's item must be of the same size.
 
 */
 
template <class Titem, typename Tindex, size_t Tgrowth_step, size_t Tmax_size, PoolType Tpool_type = PT_NORMAL, bool Tcache = false, bool Tzero = true>
 
struct Pool : PoolBase {
 
	/* Ensure Tmax_size is within the bounds of Tindex. */
 
	static_assert((uint64_t)(Tmax_size - 1) >> 8 * sizeof(Tindex) == 0);
 

	
 
	static constexpr size_t MAX_SIZE = Tmax_size; ///< Make template parameter accessible from outside
 

	
 
	using BitmapStorage = size_t;
 
	static constexpr size_t BITMAP_SIZE = std::numeric_limits<BitmapStorage>::digits;
 

	
 
	const char * const name; ///< Name of this pool
 

	
 
	size_t size;         ///< Current allocated size
 
	size_t first_free;   ///< No item with index lower than this is free (doesn't say anything about this one!)
 
	size_t first_unused; ///< This and all higher indexes are free (doesn't say anything about first_unused-1 !)
 
	size_t items;        ///< Number of used indexes (non-nullptr)
 
#ifdef WITH_ASSERT
 
	size_t checked;      ///< Number of items we checked for
 
#endif /* WITH_ASSERT */
 
	bool cleaning;       ///< True if cleaning pool (deleting all items)
 

	
 
	Titem **data;        ///< Pointer to array of pointers to Titem
 
	std::vector<BitmapStorage> used_bitmap; ///< Bitmap of used indices.
 

	
 
	Pool(const char *name);
 
	void CleanPool() override;
 

	
 
	/**
 
	 * Returns Titem with given index
 
	 * @param index of item to get
 
	 * @return pointer to Titem
 
	 * @pre index < this->first_unused
 
	 */
 
	inline Titem *Get(size_t index)
 
	{
0 comments (0 inline, 0 general)