diff options
author | Jacques Lucke <jacques@blender.org> | 2020-06-09 11:10:56 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2020-06-09 11:15:43 +0300 |
commit | d8678e02ecec9375bec1dcf1388c6fc8b4ce3ad2 (patch) | |
tree | 6e7d2a7452091877f73d413d830e6cb12e86745f /source/blender/blenlib/BLI_string_map.hh | |
parent | 50258d55e7c1360274d40e303386cf70b16c8b2f (diff) |
BLI: generally improve C++ data structures
The main focus here was to improve the docs significantly. Furthermore,
I reimplemented `Set`, `Map` and `VectorSet`. They are now (usually)
faster, simpler and more customizable. I also rewrote `Stack` to make
it more efficient by avoiding unnecessary copies.
Thanks to everyone who helped with constructive feedback.
Approved by brecht and sybren.
Differential Revision: https://developer.blender.org/D7931
Diffstat (limited to 'source/blender/blenlib/BLI_string_map.hh')
-rw-r--r-- | source/blender/blenlib/BLI_string_map.hh | 540 |
1 files changed, 0 insertions, 540 deletions
diff --git a/source/blender/blenlib/BLI_string_map.hh b/source/blender/blenlib/BLI_string_map.hh deleted file mode 100644 index caa7e16d1f3..00000000000 --- a/source/blender/blenlib/BLI_string_map.hh +++ /dev/null @@ -1,540 +0,0 @@ -/* - * 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_STRING_MAP_HH__ -#define __BLI_STRING_MAP_HH__ - -/** \file - * \ingroup bli - * - * This tries to solve the issue that a normal map with std::string as key might do many - * allocations when the keys are longer than 16 bytes (the usual small string optimization size). - * - * For now this still uses std::string, but having this abstraction in place will make it easier to - * make it more efficient later on. Also, even if we will never implement this optimization, having - * a special map with string keys can be quite handy. */ - -#include "BLI_map.hh" -#include "BLI_optional.hh" -#include "BLI_string_ref.hh" -#include "BLI_vector.hh" - -namespace BLI { - -// clang-format off - -#define ITER_SLOTS_BEGIN(HASH, ARRAY, OPTIONAL_CONST, R_ITEM, R_OFFSET) \ - uint32_t hash_copy = HASH; \ - uint32_t perturb = HASH; \ - while (true) { \ - uint32_t item_index = (hash_copy & ARRAY.slot_mask()) >> OFFSET_SHIFT; \ - uint8_t R_OFFSET = hash_copy & OFFSET_MASK; \ - uint8_t initial_offset = R_OFFSET; \ - OPTIONAL_CONST Item &R_ITEM = ARRAY.item(item_index); \ - do { - -#define ITER_SLOTS_END(R_OFFSET) \ - R_OFFSET = (R_OFFSET + 1) & OFFSET_MASK; \ - } while (R_OFFSET != initial_offset); \ - perturb >>= 5; \ - hash_copy = hash_copy * 5 + 1 + perturb; \ - } ((void)0) - -// clang-format on - -template<typename T, typename Allocator = GuardedAllocator> class StringMap { - private: - static constexpr uint32_t OFFSET_MASK = 3; - static constexpr uint32_t OFFSET_SHIFT = 2; - - class Item { - private: - static constexpr int32_t IS_EMPTY = -1; - - uint32_t m_hashes[4]; - int32_t m_indices[4]; - char m_values[sizeof(T) * 4]; - - public: - static constexpr uint slots_per_item = 4; - - Item() - { - for (uint offset = 0; offset < 4; offset++) { - m_indices[offset] = IS_EMPTY; - } - } - - ~Item() - { - for (uint offset = 0; offset < 4; offset++) { - if (this->is_set(offset)) { - destruct(this->value(offset)); - } - } - } - - Item(const Item &other) - { - for (uint offset = 0; offset < 4; offset++) { - m_indices[offset] = other.m_indices[offset]; - if (other.is_set(offset)) { - m_hashes[offset] = other.m_hashes[offset]; - new (this->value(offset)) T(*other.value(offset)); - } - } - } - - Item(Item &&other) noexcept - { - for (uint offset = 0; offset < 4; offset++) { - m_indices[offset] = other.m_indices[offset]; - if (other.is_set(offset)) { - m_hashes[offset] = other.m_hashes[offset]; - new (this->value(offset)) T(std::move(*other.value(offset))); - } - } - } - - uint32_t index(uint offset) const - { - return m_indices[offset]; - } - - uint32_t hash(uint offset) const - { - return m_hashes[offset]; - } - - T *value(uint offset) const - { - return (T *)POINTER_OFFSET(m_values, offset * sizeof(T)); - } - - bool is_set(uint offset) const - { - return m_indices[offset] >= 0; - } - - bool is_empty(uint offset) const - { - return m_indices[offset] == IS_EMPTY; - } - - bool has_hash(uint offset, uint32_t hash) const - { - BLI_assert(this->is_set(offset)); - return m_hashes[offset] == hash; - } - - bool has_exact_key(uint offset, StringRef key, const Vector<char> &chars) const - { - return key == this->get_key(offset, chars); - } - - StringRefNull get_key(uint offset, const Vector<char> &chars) const - { - const char *ptr = chars.begin() + m_indices[offset]; - uint length = *(uint *)ptr; - const char *start = ptr + sizeof(uint); - return StringRefNull(start, length); - } - - template<typename ForwardT> - void store(uint offset, uint32_t hash, uint32_t index, ForwardT &&value) - { - this->store_without_value(offset, hash, index); - new (this->value(offset)) T(std::forward<ForwardT>(value)); - } - - void store_without_value(uint offset, uint32_t hash, uint32_t index) - { - BLI_assert(!this->is_set(offset)); - m_hashes[offset] = hash; - m_indices[offset] = index; - } - }; - - using ArrayType = OpenAddressingArray<Item, 1, Allocator>; - ArrayType m_array; - Vector<char> m_chars; - - public: - StringMap() = default; - - /** - * Get the number of key-value pairs in the map. - */ - uint size() const - { - return m_array.slots_set(); - } - - /** - * Add a new element to the map. It is assumed that the key did not exist before. - */ - void add_new(StringRef key, const T &value) - { - this->add_new__impl(key, value); - } - void add_new(StringRef key, T &&value) - { - this->add_new__impl(key, std::move(value)); - } - - /** - * Add a new element to the map if the key does not exist yet. - */ - void add(StringRef key, const T &value) - { - this->add__impl(key, value); - } - void add(StringRef key, T &&value) - { - this->add__impl(key, std::move(value)); - } - - /** - * First, checks if the key exists in the map. - * If it does exist, call the modify function with a pointer to the corresponding value. - * If it does not exist, call the create function with a pointer to where the value should be - * created. - * - * Returns whatever is returned from one of the callback functions. Both callbacks have to return - * the same type. - * - * CreateValueF: Takes a pointer to where the value should be created. - * ModifyValueF: Takes a pointer to the value that should be modified. - */ - template<typename CreateValueF, typename ModifyValueF> - auto add_or_modify(StringRef key, - const CreateValueF &create_value, - const ModifyValueF &modify_value) -> decltype(create_value(nullptr)) - { - using CreateReturnT = decltype(create_value(nullptr)); - using ModifyReturnT = decltype(modify_value(nullptr)); - BLI_STATIC_ASSERT((std::is_same<CreateReturnT, ModifyReturnT>::value), - "Both callbacks should return the same type."); - - this->ensure_can_add(); - uint32_t hash = this->compute_string_hash(key); - ITER_SLOTS_BEGIN (hash, m_array, , item, offset) { - if (item.is_empty(offset)) { - m_array.update__empty_to_set(); - uint32_t index = this->save_key_in_array(key); - item.store_without_value(offset, hash, index); - T *value_ptr = item.value(offset); - return create_value(value_ptr); - } - else if (item.has_hash(offset, hash) && item.has_exact_key(offset, key, m_chars)) { - T *value_ptr = item.value(offset); - return modify_value(value_ptr); - } - } - ITER_SLOTS_END(offset); - } - - /** - * Return true when the key exists in the map, otherwise false. - */ - bool contains(StringRef key) const - { - uint32_t hash = this->compute_string_hash(key); - ITER_SLOTS_BEGIN (hash, m_array, const, item, offset) { - if (item.is_empty(offset)) { - return false; - } - else if (item.has_hash(offset, hash) && item.has_exact_key(offset, key, m_chars)) { - return true; - } - } - ITER_SLOTS_END(offset); - } - - /** - * Get a reference to the value corresponding to a key. It is assumed that the key does exist. - */ - const T &lookup(StringRef key) const - { - BLI_assert(this->contains(key)); - T *found_value = nullptr; - uint32_t hash = this->compute_string_hash(key); - ITER_SLOTS_BEGIN (hash, m_array, const, item, offset) { - if (item.is_empty(offset)) { - return *found_value; - } - else if (item.has_hash(offset, hash)) { - if (found_value == nullptr) { - /* Common case: the first slot with the correct hash contains the key. - * However, still need to iterate until the next empty slot to make sure there is no - * other key with the exact same hash. */ - /* TODO: Check if we can guarantee that every hash only exists once in some cases. */ - found_value = item.value(offset); - } - else if (item.has_exact_key(offset, key, m_chars)) { - /* Found the hash more than once, now check for actual string equality. */ - return *item.value(offset); - } - } - } - ITER_SLOTS_END(offset); - } - - T &lookup(StringRef key) - { - return const_cast<T &>(const_cast<const StringMap *>(this)->lookup(key)); - } - - /** - * Get a pointer to the value corresponding to the key. Return nullptr, if the key does not - * exist. - */ - const T *lookup_ptr(StringRef key) const - { - uint32_t hash = this->compute_string_hash(key); - ITER_SLOTS_BEGIN (hash, m_array, const, item, offset) { - if (item.is_empty(offset)) { - return nullptr; - } - else if (item.has_hash(offset, hash) && item.has_exact_key(offset, key, m_chars)) { - return item.value(offset); - } - } - ITER_SLOTS_END(offset); - } - - T *lookup_ptr(StringRef key) - { - return const_cast<T *>(const_cast<const StringMap *>(this)->lookup_ptr(key)); - } - - Optional<T> try_lookup(StringRef key) const - { - return Optional<T>::FromPointer(this->lookup_ptr(key)); - } - - /** - * Get a copy of the value corresponding to the key. If the key does not exist, return the - * default value. - */ - T lookup_default(StringRef key, const T &default_value) const - { - const T *ptr = this->lookup_ptr(key); - if (ptr != nullptr) { - return *ptr; - } - else { - return default_value; - } - } - - /** - * Return the value that corresponds to the given key. - * If it does not exist yet, create and insert it first. - */ - template<typename CreateValueF> T &lookup_or_add(StringRef key, const CreateValueF &create_value) - { - return *this->add_or_modify( - key, - [&](T *value) { return new (value) T(create_value()); }, - [](T *value) { return value; }); - } - - /** - * Return the value that corresponds to the given key. - * If it does not exist yet, insert a new default constructed value and return that. - */ - T &lookup_or_add_default(StringRef key) - { - return this->lookup_or_add(key, []() { return T(); }); - } - - /** - * Do a linear search over all items to find a key for a value. - */ - StringRefNull find_key_for_value(const T &value) const - { - for (const Item &item : m_array) { - for (uint offset = 0; offset < 4; offset++) { - if (item.is_set(offset) && value == *item.value(offset)) { - return item.get_key(offset, m_chars); - } - } - } - BLI_assert(false); - return {}; - } - - /** - * Run a function for every value in the map. - */ - template<typename FuncT> void foreach_value(const FuncT &func) - { - for (Item &item : m_array) { - for (uint offset = 0; offset < 4; offset++) { - if (item.is_set(offset)) { - func(*item.value(offset)); - } - } - } - } - - /** - * Run a function for every key in the map. - */ - template<typename FuncT> void foreach_key(const FuncT &func) - { - for (Item &item : m_array) { - for (uint offset = 0; offset < 4; offset++) { - if (item.is_set(offset)) { - StringRefNull key = item.get_key(offset, m_chars); - func(key); - } - } - } - } - - /** - * Run a function for every key-value-pair in the map. - */ - template<typename FuncT> void foreach_item(const FuncT &func) - { - for (Item &item : m_array) { - for (uint offset = 0; offset < 4; offset++) { - if (item.is_set(offset)) { - StringRefNull key = item.get_key(offset, m_chars); - T &value = *item.value(offset); - func(key, value); - } - } - } - } - - template<typename FuncT> void foreach_item(const FuncT &func) const - { - for (const Item &item : m_array) { - for (uint offset = 0; offset < 4; offset++) { - if (item.is_set(offset)) { - StringRefNull key = item.get_key(offset, m_chars); - const T &value = *item.value(offset); - func(key, value); - } - } - } - } - - private: - uint32_t compute_string_hash(StringRef key) const - { - /* TODO: check if this can be optimized more because we know the key length already. */ - uint32_t hash = 5381; - for (char c : key) { - hash = hash * 33 + c; - } - return hash; - } - - uint32_t save_key_in_array(StringRef key) - { - uint index = m_chars.size(); - uint string_size = key.size(); - m_chars.extend(ArrayRef<char>((char *)&string_size, sizeof(uint))); - m_chars.extend(key); - m_chars.append('\0'); - return index; - } - - StringRefNull key_from_index(uint32_t index) const - { - const char *ptr = m_chars.begin() + index; - uint length = *(uint *)ptr; - const char *start = ptr + sizeof(uint); - return StringRefNull(start, length); - } - - void ensure_can_add() - { - if (UNLIKELY(m_array.should_grow())) { - this->grow(this->size() + 1); - } - } - - BLI_NOINLINE void grow(uint min_usable_slots) - { - ArrayType new_array = m_array.init_reserved(min_usable_slots); - for (Item &old_item : m_array) { - for (uint offset = 0; offset < 4; offset++) { - if (old_item.is_set(offset)) { - this->add_after_grow( - *old_item.value(offset), old_item.hash(offset), old_item.index(offset), new_array); - } - } - } - m_array = std::move(new_array); - } - - void add_after_grow(T &value, uint32_t hash, uint32_t index, ArrayType &new_array) - { - ITER_SLOTS_BEGIN (hash, new_array, , item, offset) { - if (item.is_empty(offset)) { - item.store(offset, hash, index, std::move(value)); - return; - } - } - ITER_SLOTS_END(offset); - } - - template<typename ForwardT> bool add__impl(StringRef key, ForwardT &&value) - { - this->ensure_can_add(); - uint32_t hash = this->compute_string_hash(key); - ITER_SLOTS_BEGIN (hash, m_array, , item, offset) { - if (item.is_empty(offset)) { - uint32_t index = this->save_key_in_array(key); - item.store(offset, hash, index, std::forward<ForwardT>(value)); - m_array.update__empty_to_set(); - return true; - } - else if (item.has_hash(offset, hash) && item.has_exact_key(offset, key, m_chars)) { - return false; - } - } - ITER_SLOTS_END(offset); - } - - template<typename ForwardT> void add_new__impl(StringRef key, ForwardT &&value) - { - BLI_assert(!this->contains(key)); - this->ensure_can_add(); - uint32_t hash = this->compute_string_hash(key); - ITER_SLOTS_BEGIN (hash, m_array, , item, offset) { - if (item.is_empty(offset)) { - uint32_t index = this->save_key_in_array(key); - item.store(offset, hash, index, std::forward<ForwardT>(value)); - m_array.update__empty_to_set(); - return; - } - } - ITER_SLOTS_END(offset); - } -}; - -#undef ITER_SLOTS_BEGIN -#undef ITER_SLOTS_END - -} // namespace BLI - -#endif /* __BLI_STRING_MAP_HH__ */ |