diff options
Diffstat (limited to 'source/blender/blenlib/BLI_open_addressing.hh')
-rw-r--r-- | source/blender/blenlib/BLI_open_addressing.hh | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/source/blender/blenlib/BLI_open_addressing.hh b/source/blender/blenlib/BLI_open_addressing.hh new file mode 100644 index 00000000000..fa2ea6d8f94 --- /dev/null +++ b/source/blender/blenlib/BLI_open_addressing.hh @@ -0,0 +1,305 @@ +/* + * This program 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; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __BLI_OPEN_ADDRESSING_HH__ +#define __BLI_OPEN_ADDRESSING_HH__ + +/** \file + * \ingroup bli + * + * This class offers a useful abstraction for other containers that implement hash tables using + * open addressing. It handles the following aspects: + * - Allocation and deallocation of the open addressing array. + * - Optional small object optimization. + * - Keeps track of how many elements and dummies are in the table. + * + * The nice thing about this abstraction is that it does not get in the way of any performance + * optimizations. The data that is actually stored in the table is still fully defined by the + * actual hash table implementation. + */ + +#include <cmath> + +#include "BLI_allocator.hh" +#include "BLI_math_base.h" +#include "BLI_memory_utils.hh" +#include "BLI_utildefines.h" + +namespace BLI { + +template<typename Item, uint32_t ItemsInSmallStorage = 1, typename Allocator = GuardedAllocator> +class OpenAddressingArray { + private: + static constexpr uint32_t slots_per_item = Item::slots_per_item; + static constexpr float max_load_factor = 0.5f; + + /* Invariants: + * 2^m_item_exponent = m_item_amount + * m_item_amount * slots_per_item = m_slots_total + * m_slot_mask = m_slots_total - 1 + * m_slots_set_or_dummy < m_slots_total + */ + + /* Array containing the actual hash table. Might be a pointer to the inlined storage. */ + Item *m_items; + /* Number of items in the hash table. Must be a power of two. */ + uint32_t m_item_amount; + /* Exponent of the current item amount. */ + uint8_t m_item_exponent; + /* Number of elements that could be stored in the table when the load factor is 1. */ + uint32_t m_slots_total; + /* Number of elements that are not empty. */ + uint32_t m_slots_set_or_dummy; + /* Number of dummy entries. */ + uint32_t m_slots_dummy; + /* Max number of slots that can be non-empty according to the load factor. */ + uint32_t m_slots_usable; + /* Can be used to map a hash value into the range of valid slot indices. */ + uint32_t m_slot_mask; + Allocator m_allocator; + AlignedBuffer<sizeof(Item) * ItemsInSmallStorage, alignof(Item)> m_local_storage; + + public: + explicit OpenAddressingArray(uint8_t item_exponent = 0) + { + m_slots_total = ((uint32_t)1 << item_exponent) * slots_per_item; + m_slots_set_or_dummy = 0; + m_slots_dummy = 0; + m_slots_usable = (uint32_t)((float)m_slots_total * max_load_factor); + m_slot_mask = m_slots_total - 1; + m_item_amount = m_slots_total / slots_per_item; + m_item_exponent = item_exponent; + + if (m_item_amount <= ItemsInSmallStorage) { + m_items = this->small_storage(); + } + else { + m_items = (Item *)m_allocator.allocate_aligned( + (uint32_t)sizeof(Item) * m_item_amount, std::alignment_of<Item>::value, __func__); + } + + for (uint32_t i = 0; i < m_item_amount; i++) { + new (m_items + i) Item(); + } + } + + ~OpenAddressingArray() + { + if (m_items != nullptr) { + for (uint32_t i = 0; i < m_item_amount; i++) { + m_items[i].~Item(); + } + if (!this->is_in_small_storage()) { + m_allocator.deallocate((void *)m_items); + } + } + } + + OpenAddressingArray(const OpenAddressingArray &other) + { + m_slots_total = other.m_slots_total; + m_slots_set_or_dummy = other.m_slots_set_or_dummy; + m_slots_dummy = other.m_slots_dummy; + m_slots_usable = other.m_slots_usable; + m_slot_mask = other.m_slot_mask; + m_item_amount = other.m_item_amount; + m_item_exponent = other.m_item_exponent; + + if (m_item_amount <= ItemsInSmallStorage) { + m_items = this->small_storage(); + } + else { + m_items = (Item *)m_allocator.allocate_aligned( + sizeof(Item) * m_item_amount, std::alignment_of<Item>::value, __func__); + } + + uninitialized_copy_n(other.m_items, m_item_amount, m_items); + } + + OpenAddressingArray(OpenAddressingArray &&other) noexcept + { + m_slots_total = other.m_slots_total; + m_slots_set_or_dummy = other.m_slots_set_or_dummy; + m_slots_dummy = other.m_slots_dummy; + m_slots_usable = other.m_slots_usable; + m_slot_mask = other.m_slot_mask; + m_item_amount = other.m_item_amount; + m_item_exponent = other.m_item_exponent; + if (other.is_in_small_storage()) { + m_items = this->small_storage(); + uninitialized_relocate_n(other.m_items, m_item_amount, m_items); + } + else { + m_items = other.m_items; + } + + other.m_items = nullptr; + other.~OpenAddressingArray(); + new (&other) OpenAddressingArray(); + } + + OpenAddressingArray &operator=(const OpenAddressingArray &other) + { + if (this == &other) { + return *this; + } + this->~OpenAddressingArray(); + new (this) OpenAddressingArray(other); + return *this; + } + + OpenAddressingArray &operator=(OpenAddressingArray &&other) + { + if (this == &other) { + return *this; + } + this->~OpenAddressingArray(); + new (this) OpenAddressingArray(std::move(other)); + return *this; + } + + /* Prepare a new array that can hold a minimum of min_usable_slots elements. All entries are + * empty. */ + OpenAddressingArray init_reserved(uint32_t min_usable_slots) const + { + float min_total_slots = (float)min_usable_slots / max_load_factor; + uint32_t min_total_items = (uint32_t)std::ceil(min_total_slots / (float)slots_per_item); + uint8_t item_exponent = (uint8_t)log2_ceil_u(min_total_items); + OpenAddressingArray grown(item_exponent); + grown.m_slots_set_or_dummy = this->slots_set(); + return grown; + } + + /** + * Amount of items in the array times the number of slots per item. + */ + uint32_t slots_total() const + { + return m_slots_total; + } + + /** + * Amount of slots that are initialized with some value that is not empty or dummy. + */ + uint32_t slots_set() const + { + return m_slots_set_or_dummy - m_slots_dummy; + } + + /** + * Amount of slots that can be used before the array should grow. + */ + uint32_t slots_usable() const + { + return m_slots_usable; + } + + /** + * Update the counters after one empty element is used for a newly added element. + */ + void update__empty_to_set() + { + m_slots_set_or_dummy++; + } + + /** + * Update the counters after one previously dummy element becomes set. + */ + void update__dummy_to_set() + { + m_slots_dummy--; + } + + /** + * Update the counters after one previously set element becomes a dummy. + */ + void update__set_to_dummy() + { + m_slots_dummy++; + } + + /** + * Access the current slot mask for this array. + */ + uint32_t slot_mask() const + { + return m_slot_mask; + } + + /** + * Access the item for a specific item index. + * Note: The item index is not necessarily the slot index. + */ + const Item &item(uint32_t item_index) const + { + return m_items[item_index]; + } + + Item &item(uint32_t item_index) + { + return m_items[item_index]; + } + + uint8_t item_exponent() const + { + return m_item_exponent; + } + + uint32_t item_amount() const + { + return m_item_amount; + } + + bool should_grow() const + { + return m_slots_set_or_dummy >= m_slots_usable; + } + + Item *begin() + { + return m_items; + } + + Item *end() + { + return m_items + m_item_amount; + } + + const Item *begin() const + { + return m_items; + } + + const Item *end() const + { + return m_items + m_item_amount; + } + + private: + Item *small_storage() const + { + return reinterpret_cast<Item *>((char *)m_local_storage.ptr()); + } + + bool is_in_small_storage() const + { + return m_items == this->small_storage(); + } +}; + +} // namespace BLI + +#endif /* __BLI_OPEN_ADDRESSING_HH__ */ |