diff --git a/CMakeLists.txt b/CMakeLists.txt index 149e23ab..3c23156b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ set(INTERNAL_SOURCE_FILES internal/AtmosphereRef.h internal/FreelistAlloc.h internal/FreelistAlloc.cpp internal/HashMap32.h + internal/HashSet32.h internal/RadCacheRef.h internal/RadCacheRef.cpp internal/RastState.h diff --git a/internal/HashMap32.h b/internal/HashMap32.h index e9c0b7bd..2bf50ba7 100644 --- a/internal/HashMap32.h +++ b/internal/HashMap32.h @@ -1,91 +1,13 @@ #pragma once -#include -#include -#include // for std::equal_to -#include +#include "HashSet32.h" namespace Ray { -inline uint32_t _lua_hash(void const *v, const uint32_t l) { - uint32_t i, step = (l >> 5u) + 1; - uint32_t h = l + (l >= 4 ? *(uint32_t *)v : 0); - for (i = l; i >= step; i -= step) { - h = h ^ ((h << 5u) + (h >> 2u) + ((unsigned char *)v)[i - 1]); - } - return h; -} - -inline uint32_t _str_hash(const char *s) { - const uint32_t A = 54059; - const uint32_t B = 76963; - // const uint32_t C = 86969; - const uint32_t FIRSTH = 37; - - uint32_t h = FIRSTH; - while (*s) { - h = (h * A) ^ (s[0] * B); - s++; - } - return h; -} - -inline uint32_t _str_hash_len(const char *s, size_t len) { - const uint32_t A = 54059; - const uint32_t B = 76963; - // const uint32_t C = 86969; - const uint32_t FIRSTH = 37; - - uint32_t h = FIRSTH; - while (len) { - h = (h * A) ^ (s[0] * B); - s++; - len--; - } - return h; -} - -template class Hash { - public: - uint32_t operator()(const K &k) const { return _lua_hash(&k, sizeof(K)); } -}; - -template <> class Hash { - public: - uint32_t operator()(const char *s) const { return _str_hash(s); } -}; - -template <> class Hash { - public: - uint32_t operator()(const std::string &s) const { return _str_hash(s.c_str()); } -}; - -/*template <> class Hash { - public: - uint32_t operator()(const String &s) const { return _str_hash(s.c_str()); } - - uint32_t operator()(const StringPart &s) const { return _str_hash_len(s.str, s.len); } - - uint32_t operator()(const char *s) const { return _str_hash(s); } -};*/ - -template class Equal { - std::equal_to eq_; +template constexpr int max_value = (A > B) ? A : B; - public: - bool operator()(const K &k1, const K &k2) const { return eq_(k1, k2); } -}; - -template <> class Equal { - public: - bool operator()(const char *k1, const char *k2) const { return strcmp(k1, k2) == 0; } -}; - -/*template <> class Equal { - public: - template bool operator()(const String &k1, const K2 &k2) const { return k1 == k2; } -};*/ - -template , typename KeyEqual = Equal> class HashMap32 { +template , typename KeyEqual = Equal, + typename Allocator = aligned_allocator>> +class HashMap32 : HashFunc, KeyEqual, Allocator { static const uint8_t OccupiedBit = 0b10000000; static const uint8_t HashMask = 0b01111111; @@ -100,43 +22,56 @@ template , typename KeyEqual uint8_t *ctrl_; Node *nodes_; uint32_t capacity_, size_; - HashFunc hash_func_; - KeyEqual key_equal_; - - public: - explicit HashMap32(const HashFunc &hash_func = HashFunc(), const KeyEqual &key_equal = KeyEqual()) noexcept - : ctrl_(nullptr), nodes_(nullptr), capacity_(0), size_(0), hash_func_(hash_func), key_equal_(key_equal) {} - - explicit HashMap32(uint32_t capacity, const HashFunc &hash_func = HashFunc(), - const KeyEqual &key_equal = KeyEqual()) - : hash_func_(hash_func), key_equal_(key_equal) { - // Check if power of 2 - assert((capacity & (capacity - 1)) == 0); - - uint32_t mem_size = capacity; - if (mem_size % alignof(Node)) { - mem_size += alignof(Node) - (mem_size % alignof(Node)); - } - uint32_t node_begin = mem_size; - mem_size += sizeof(Node) * capacity; - - ctrl_ = new uint8_t[mem_size]; - nodes_ = (Node *)&ctrl_[node_begin]; - assert(uintptr_t(nodes_) % alignof(Node) == 0); + static uint32_t ctrl_size(const uint32_t cap) { + return alignof(Node) * ((cap + alignof(Node) - 1) / alignof(Node)); + } + static uint32_t mem_size(const uint32_t cap) { return ctrl_size(cap) + sizeof(Node) * cap; } - memset(ctrl_, 0, capacity); + public: + explicit HashMap32(const HashFunc &hash_func = HashFunc(), const KeyEqual &key_equal = KeyEqual(), + const Allocator &alloc = Allocator()) noexcept + : HashFunc(hash_func), KeyEqual(key_equal), Allocator(alloc), ctrl_(nullptr), nodes_(nullptr), capacity_(0), + size_(0) {} + + explicit HashMap32(const uint32_t capacity, const HashFunc &hash_func = HashFunc(), + const KeyEqual &key_equal = KeyEqual(), const Allocator &alloc = Allocator()) + : HashFunc(hash_func), KeyEqual(key_equal), Allocator(alloc), ctrl_(nullptr), nodes_(nullptr), capacity_(0), + size_(0) { + ReserveRealloc(capacity); + } - capacity_ = capacity; - size_ = 0; + explicit HashMap32(std::initializer_list> l, const HashFunc &hash_func = HashFunc(), + const KeyEqual &key_equal = KeyEqual(), const Allocator &alloc = Allocator()) noexcept + : HashFunc(hash_func), KeyEqual(key_equal), Allocator(alloc), ctrl_(nullptr), nodes_(nullptr), capacity_(0), + size_(0) { + ReserveRealloc(uint32_t(l.size())); + for (auto it = l.begin(); it != l.end(); ++it) { + Insert(it->first, it->second); + } } HashMap32(const HashMap32 &rhs) = delete; HashMap32 &operator=(const HashMap32 &rhs) = delete; + HashMap32(HashMap32 &&rhs) noexcept { (*this) = std::move(rhs); } + HashMap32 &operator=(HashMap32 &&rhs) noexcept { + if (this == &rhs) { + return (*this); + } + Allocator::operator=(static_cast(rhs)); + HashFunc::operator=(static_cast(rhs)); + KeyEqual::operator=(static_cast(rhs)); + ctrl_ = std::exchange(rhs.ctrl_, nullptr); + nodes_ = std::exchange(rhs.nodes_, nullptr); + capacity_ = std::exchange(rhs.capacity_, 0); + size_ = std::exchange(rhs.size_, 0); + return (*this); + } + ~HashMap32() { clear(); - delete[] ctrl_; + this->deallocate(ctrl_, mem_size(capacity_)); } uint32_t size() const { return size_; } @@ -145,16 +80,16 @@ template , typename KeyEqual void clear() { for (uint32_t i = 0; i < capacity_ && size_; i++) { if (ctrl_[i] & OccupiedBit) { - size_--; + --size_; nodes_[i].key.~K(); nodes_[i].val.~V(); } } - memset(ctrl_, 0, capacity_); + assert(size_ == 0); } - void reserve(uint32_t capacity) { ReserveRealloc(capacity); } + void reserve(const uint32_t capacity) { ReserveRealloc(capacity); } V &operator[](const K &key) { V *v = Find(key); @@ -174,7 +109,7 @@ template , typename KeyEqual } bool Insert(K &&key, V &&val) { - uint32_t hash = hash_func_(key); + const uint32_t hash = HashFunc::operator()(key); const V *v = Find(hash, key); if (v) { @@ -193,19 +128,18 @@ template , typename KeyEqual } V *InsertNoCheck(const K &key) { - uint32_t hash = hash_func_(key); + const uint32_t hash = HashFunc::operator()(key); return InsertInternal(hash, key); } bool Erase(const K &key) { - uint32_t hash = hash_func_(key); - uint8_t ctrl = OccupiedBit | (hash & HashMask); + const uint32_t hash = HashFunc::operator()(key); + const uint8_t ctrl_to_find = OccupiedBit | (hash & HashMask); uint32_t i = hash & (capacity_ - 1); - uint32_t end = i; + const uint32_t end = i; while (ctrl_[i]) { - if (ctrl_[i] == ctrl && nodes_[i].hash == hash && key_equal_(nodes_[i].key, key)) { - + if (ctrl_[i] == ctrl_to_find && nodes_[i].hash == hash && KeyEqual::operator()(nodes_[i].key, key)) { --size_; ctrl_[i] = HashMask; nodes_[i].key.~K(); @@ -213,34 +147,32 @@ template , typename KeyEqual return true; } - i = (i + 1) & (capacity_ - 1); - if (i == end) + if (i == end) { break; + } } return false; } - template V *Find(const K2 &key) { - const uint32_t hash = hash_func_(key); - return Find(hash, key); - } + template const V *Find(const K2 &key) const { return Find(HashFunc::operator()(key), key); } + + template V *Find(const K2 &key) { return Find(HashFunc::operator()(key), key); } - template V *Find(uint32_t hash, const K2 &key) { + template const V *Find(const uint32_t hash, const K2 &key) const { if (!capacity_) { return nullptr; } - const uint8_t ctrl = OccupiedBit | (hash & HashMask); + const uint8_t ctrl_to_find = OccupiedBit | (hash & HashMask); uint32_t i = hash & (capacity_ - 1); const uint32_t end = i; while (ctrl_[i]) { - if (ctrl_[i] == ctrl && nodes_[i].hash == hash && key_equal_(nodes_[i].key, key)) { + if (ctrl_[i] == ctrl_to_find && nodes_[i].hash == hash && KeyEqual::operator()(nodes_[i].key, key)) { return &nodes_[i].val; } - i = (i + 1) & (capacity_ - 1); if (i == end) { break; @@ -250,6 +182,10 @@ template , typename KeyEqual return nullptr; } + template V *Find(const uint32_t hash, const K2 &key) { + return const_cast(const_cast(this)->Find(hash, key)); + } + Node *GetOrNull(const uint32_t index) { if (index < capacity_ && (ctrl_[index / 8] & (1u << (index % 8)))) { return &nodes_[index]; @@ -266,7 +202,7 @@ template , typename KeyEqual } } - class HashMap32Iterator : public std::iterator { + class HashMap32Iterator { friend class HashMap32; HashMap32 *container_; @@ -276,6 +212,12 @@ template , typename KeyEqual : container_(container), index_(index) {} public: + using iterator_category = std::forward_iterator_tag; + using value_type = Node; + using difference_type = std::ptrdiff_t; + using pointer = Node *; + using reference = Node &; + Node &operator*() { return container_->at(index_); } Node *operator->() { return &container_->at(index_); } HashMap32Iterator &operator++() { @@ -298,7 +240,7 @@ template , typename KeyEqual bool operator!=(const HashMap32Iterator &rhs) { return index_ != rhs.index_; } }; - class HashMap32ConstIterator : public std::iterator { + class HashMap32ConstIterator { friend class HashMap32; const HashMap32 *container_; @@ -308,6 +250,12 @@ template , typename KeyEqual : container_(container), index_(index) {} public: + using iterator_category = std::forward_iterator_tag; + using value_type = Node; + using difference_type = std::ptrdiff_t; + using pointer = Node *; + using reference = Node &; + const Node &operator*() { return container_->at(index_); } const Node *operator->() { return &container_->at(index_); } HashMap32ConstIterator &operator++() { @@ -334,16 +282,17 @@ template , typename KeyEqual using const_iterator = HashMap32ConstIterator; iterator begin() { - for (uint32_t i = 0; i < capacity_; i++) { + for (uint32_t i = 0; i < capacity_; ++i) { if (ctrl_[i] & OccupiedBit) { return iterator(this, i); } } return end(); } + const_iterator begin() const { return cbegin(); } const_iterator cbegin() const { - for (uint32_t i = 0; i < capacity_; i++) { + for (uint32_t i = 0; i < capacity_; ++i) { if (ctrl_[i] & OccupiedBit) { return const_iterator(this, i); } @@ -352,92 +301,68 @@ template , typename KeyEqual } iterator end() { return iterator(this, capacity_); } - + const_iterator end() const { return const_iterator(this, capacity_); } const_iterator cend() const { return const_iterator(this, capacity_); } iterator iter_at(uint32_t i) { return iterator(this, i); } - const_iterator citer_at(uint32_t i) const { return const_iterator(this, i); } - Node &at(uint32_t index) { + Node &at(const uint32_t index) { assert((ctrl_[index] & OccupiedBit) && "Invalid index!"); return nodes_[index]; } - const Node &at(uint32_t index) const { + const Node &at(const uint32_t index) const { assert((ctrl_[index] & OccupiedBit) && "Invalid index!"); return nodes_[index]; } - private: - void CheckRealloc() { - if ((size_ + 1) > uint32_t(0.8 * capacity_)) { - uint8_t *old_ctrl = ctrl_; - Node *old_nodes = nodes_; - uint32_t old_capacity = capacity_; - - if (capacity_) { - capacity_ *= 2; - } else { - capacity_ = 8; - } - size_ = 0; - - uint32_t mem_size = capacity_; - mem_size += (mem_size % alignof(Node)); - - uint32_t node_begin = mem_size; - mem_size += sizeof(Node) * capacity_; + iterator erase(const iterator it) { + const uint32_t next = NextOccupied(it.index_); - ctrl_ = new uint8_t[mem_size]; - if (!ctrl_ || mem_size < capacity_) { - return; - } - nodes_ = (Node *)&ctrl_[node_begin]; - - memset(ctrl_, 0, capacity_); + --size_; + ctrl_[it.index_] = HashMask; + nodes_[it.index_].key.~K(); + nodes_[it.index_].val.~V(); - for (uint32_t i = 0; i < old_capacity; i++) { - if (old_ctrl[i] & OccupiedBit) { - InsertInternal(old_nodes[i].hash, std::move(old_nodes[i].key), std::move(old_nodes[i].val)); - } - } + return iter_at(next); + } - delete[] old_ctrl; + private: + void CheckRealloc() { + if ((size_ + 1) > uint32_t(0.8f * capacity_)) { + ReserveRealloc(capacity_ * 2); } } void ReserveRealloc(uint32_t desired_capacity) { - if (capacity_ < desired_capacity) { + if (!capacity_ || capacity_ < desired_capacity) { uint8_t *old_ctrl = ctrl_; Node *old_nodes = nodes_; uint32_t old_capacity = capacity_; - if (!capacity_) + if (!capacity_) { capacity_ = 8; - while (capacity_ < desired_capacity) + } + while (capacity_ < desired_capacity) { capacity_ *= 2; - + } size_ = 0; - uint32_t mem_size = capacity_; - mem_size += (mem_size % alignof(Node)); - - uint32_t node_begin = mem_size; - mem_size += sizeof(Node) * capacity_; - - ctrl_ = new uint8_t[mem_size]; - nodes_ = (Node *)&ctrl_[node_begin]; - + ctrl_ = this->allocate(mem_size(capacity_)); + if (!ctrl_) { + return; + } + nodes_ = reinterpret_cast(&ctrl_[ctrl_size(capacity_)]); memset(ctrl_, 0, capacity_); - for (uint32_t i = 0; i < old_capacity; i++) { + for (uint32_t i = 0; i < old_capacity; ++i) { if (old_ctrl[i] & OccupiedBit) { InsertInternal(old_nodes[i].hash, std::move(old_nodes[i].key), std::move(old_nodes[i].val)); } } - delete[] old_ctrl; + this->deallocate(old_ctrl, mem_size(old_capacity)); } } @@ -449,7 +374,7 @@ template , typename KeyEqual i = (i + 1) & (capacity_ - 1); } - size_++; + ++size_; ctrl_[i] = OccupiedBit | (hash & HashMask); nodes_[i].hash = hash; new (&nodes_[i].key) K(key); @@ -483,7 +408,7 @@ template , typename KeyEqual i = (i + 1) & (capacity_ - 1); } - size_++; + ++size_; ctrl_[i] = OccupiedBit | (hash & HashMask); nodes_[i].hash = hash; new (&nodes_[i].key) K(std::forward(key)); @@ -492,7 +417,7 @@ template , typename KeyEqual uint32_t NextOccupied(uint32_t index) const { assert((ctrl_[index] & OccupiedBit) && "Invalid index!"); - for (uint32_t i = index + 1; i < capacity_; i++) { + for (uint32_t i = index + 1; i < capacity_; ++i) { if (ctrl_[i] & OccupiedBit) { return i; } diff --git a/internal/HashSet32.h b/internal/HashSet32.h new file mode 100644 index 00000000..b20c440e --- /dev/null +++ b/internal/HashSet32.h @@ -0,0 +1,479 @@ +#pragma once + +#include +#include +#include +#include // for std::equal_to +#include +#include + +#include "simd/aligned_allocator.h" + +namespace Ray { +inline uint32_t _lua_hash(void const *v, const uint32_t l) { + uint32_t i, step = (l >> 5u) + 1; + uint32_t h = l + (l >= 4 ? *(uint32_t *)v : 0); + for (i = l; i >= step; i -= step) { + h = h ^ ((h << 5u) + (h >> 2u) + ((unsigned char *)v)[i - 1]); + } + return h; +} + +inline uint32_t _str_hash(const char *s) { + const uint32_t A = 54059; + const uint32_t B = 76963; + // const uint32_t C = 86969; + const uint32_t FIRSTH = 37; + + uint32_t h = FIRSTH; + while (*s) { + h = (h * A) ^ (s[0] * B); + s++; + } + return h; +} + +inline uint32_t _str_hash_len(const char *s, size_t len) { + const uint32_t A = 54059; + const uint32_t B = 76963; + // const uint32_t C = 86969; + const uint32_t FIRSTH = 37; + + uint32_t h = FIRSTH; + while (len) { + h = (h * A) ^ (s[0] * B); + s++; + len--; + } + return h; +} + +template class Hash { + public: + uint32_t operator()(const K &k) const { return _lua_hash(&k, sizeof(K)); } +}; + +template <> class Hash { + public: + uint32_t operator()(const char *s) const { return _str_hash(s); } +}; + +template <> class Hash { + public: + uint32_t operator()(const char *s) const { return _str_hash(s); } +}; + +template <> class Hash { + public: + uint32_t operator()(const std::string &s) const { return _str_hash_len(s.c_str(), s.length()); } + uint32_t operator()(const char *s) const { return _str_hash(s); } +}; + +template class Equal : std::equal_to { + public: + bool operator()(const K &k1, const K &k2) const { return std::equal_to::operator()(k1, k2); } +}; + +template <> class Equal { + public: + bool operator()(const char *k1, const char *k2) const { return strcmp(k1, k2) == 0; } +}; + +template <> class Equal { + public: + bool operator()(const char *k1, const char *k2) const { return strcmp(k1, k2) == 0; } +}; + +template <> class Equal { + public: + template bool operator()(const std::string &k1, const K2 &k2) const { return k1 == k2; } + bool operator()(const std::string &k1, const char *k2) const { return k1 == k2; } +}; + +template , typename KeyEqual = Equal, + typename Allocator = aligned_allocator> +class HashSet32 : HashFunc, KeyEqual, Allocator { + static const uint8_t OccupiedBit = 0b10000000; + static const uint8_t HashMask = 0b01111111; + + public: + struct Node { + uint32_t hash; + K key; + }; + + private: + uint8_t *ctrl_; + Node *nodes_; + uint32_t capacity_, size_; + + static uint32_t ctrl_size(const uint32_t cap) { + return alignof(Node) * ((cap + alignof(Node) - 1) / alignof(Node)); + } + static uint32_t mem_size(const uint32_t cap) { return ctrl_size(cap) + sizeof(Node) * cap; } + + public: + explicit HashSet32(const HashFunc &hash_func = HashFunc(), const KeyEqual &key_equal = KeyEqual(), + const Allocator &alloc = Allocator()) noexcept + : HashFunc(hash_func), KeyEqual(key_equal), Allocator(alloc), ctrl_(nullptr), nodes_(nullptr), capacity_(0), + size_(0) {} + + explicit HashSet32(const uint32_t capacity, const HashFunc &hash_func = HashFunc(), + const KeyEqual &key_equal = KeyEqual(), const Allocator &alloc = Allocator()) + : HashFunc(hash_func), KeyEqual(key_equal), Allocator(alloc), ctrl_(nullptr), nodes_(nullptr), capacity_(0), + size_(0) { + ReserveRealloc(capacity); + } + + explicit HashSet32(std::initializer_list l, const HashFunc &hash_func = HashFunc(), + const KeyEqual &key_equal = KeyEqual(), const Allocator &alloc = Allocator()) noexcept + : HashFunc(hash_func), KeyEqual(key_equal), Allocator(alloc), ctrl_(nullptr), nodes_(nullptr), capacity_(0), + size_(0) { + ReserveRealloc(uint32_t(l.size())); + for (auto it = l.begin(); it != l.end(); ++it) { + Insert(*it); + } + } + + HashSet32(const HashSet32 &rhs) = delete; + HashSet32 &operator=(const HashSet32 &rhs) = delete; + + HashSet32(HashSet32 &&rhs) noexcept { (*this) = std::move(rhs); } + HashSet32 &operator=(HashSet32 &&rhs) noexcept { + if (this == &rhs) { + return (*this); + } + Allocator::operator=(static_cast(rhs)); + HashFunc::operator=(static_cast(rhs)); + KeyEqual::operator=(static_cast(rhs)); + ctrl_ = std::exchange(rhs.ctrl_, nullptr); + nodes_ = std::exchange(rhs.nodes_, nullptr); + capacity_ = std::exchange(rhs.capacity_, 0); + size_ = std::exchange(rhs.size_, 0); + return (*this); + } + + ~HashSet32() { + clear(); + this->deallocate(ctrl_, mem_size(capacity_)); + } + + uint32_t size() const { return size_; } + uint32_t capacity() const { return capacity_; } + + bool empty() const { return size_ == 0; } + + void clear() { + for (uint32_t i = 0; i < capacity_ && size_; i++) { + if (ctrl_[i] & OccupiedBit) { + --size_; + nodes_[i].key.~K(); + } + } + memset(ctrl_, 0, capacity_); + assert(size_ == 0); + } + + void reserve(const uint32_t capacity) { ReserveRealloc(capacity); } + + bool Insert(const K &key) { + const K *k = Find(key); + if (k) { + return false; + } + InsertNoCheck(key); + return true; + } + + bool Insert(K &&key) { + const uint32_t hash = HashFunc::operator()(key); + + const K *k = Find(hash, key); + if (k) { + return false; + } + InsertInternal(hash, std::forward(key)); + return true; + } + + Node *InsertNoCheck(const K &key) { + const uint32_t hash = HashFunc::operator()(key); + return InsertInternal(hash, key); + } + + bool Erase(const K &key) { + const uint32_t hash = HashFunc::operator()(key); + const uint8_t ctrl_to_find = OccupiedBit | (hash & HashMask); + + uint32_t i = hash & (capacity_ - 1); + const uint32_t end = i; + while (ctrl_[i]) { + if (ctrl_[i] == ctrl_to_find && nodes_[i].hash == hash && KeyEqual::operator()(nodes_[i].key, key)) { + --size_; + ctrl_[i] = HashMask; + nodes_[i].key.~K(); + + return true; + } + i = (i + 1) & (capacity_ - 1); + if (i == end) { + break; + } + } + + return false; + } + + template const K *Find(const K2 &key) const { return Find(HashFunc::operator()(key), key); } + template K *Find(const K2 &key) { return Find(HashFunc::operator()(key), key); } + + template const K *Find(const uint32_t hash, const K2 &key) const { + if (!capacity_) { + return nullptr; + } + + const uint8_t ctrl_to_find = OccupiedBit | (hash & HashMask); + + uint32_t i = hash & (capacity_ - 1); + const uint32_t end = i; + while (ctrl_[i]) { + if (ctrl_[i] == ctrl_to_find && nodes_[i].hash == hash && KeyEqual::operator()(nodes_[i].key, key)) { + return &nodes_[i].key; + } + i = (i + 1) & (capacity_ - 1); + if (i == end) { + break; + } + } + + return nullptr; + } + + template K *Find(const uint32_t hash, const K2 &key) { + return const_cast(const_cast(this)->Find(hash, key)); + } + + Node *GetOrNull(const uint32_t index) { + if (index < capacity_ && (ctrl_[index / 8] & (1u << (index % 8)))) { + return &nodes_[index]; + } else { + return nullptr; + } + } + + const Node *GetOrNull(const uint32_t index) const { + if (index < capacity_ && (ctrl_[index / 8] & (1u << (index % 8)))) { + return &nodes_[index]; + } else { + return nullptr; + } + } + + class HashSet32Iterator { + friend class HashSet32; + + HashSet32 *container_; + uint32_t index_; + + HashSet32Iterator(HashSet32 *container, uint32_t index) + : container_(container), index_(index) {} + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = K; + using difference_type = std::ptrdiff_t; + using pointer = K *; + using reference = K &; + + K &operator*() { return container_->at(index_).key; } + K *operator->() { return &container_->at(index_).key; } + HashSet32Iterator &operator++() { + index_ = container_->NextOccupied(index_); + return *this; + } + HashSet32Iterator operator++(int) { + HashSet32Iterator tmp(*this); + ++(*this); + return tmp; + } + + uint32_t index() const { return index_; } + + bool operator<(const HashSet32Iterator &rhs) { return index_ < rhs.index_; } + bool operator<=(const HashSet32Iterator &rhs) { return index_ <= rhs.index_; } + bool operator>(const HashSet32Iterator &rhs) { return index_ > rhs.index_; } + bool operator>=(const HashSet32Iterator &rhs) { return index_ >= rhs.index_; } + bool operator==(const HashSet32Iterator &rhs) { return index_ == rhs.index_; } + bool operator!=(const HashSet32Iterator &rhs) { return index_ != rhs.index_; } + }; + + class HashSet32ConstIterator { + friend class HashSet32; + + const HashSet32 *container_; + uint32_t index_; + + HashSet32ConstIterator(const HashSet32 *container, uint32_t index) + : container_(container), index_(index) {} + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = K; + using difference_type = std::ptrdiff_t; + using pointer = K *; + using reference = K &; + + const K &operator*() { return container_->at(index_).key; } + const K *operator->() { return &container_->at(index_).key; } + HashSet32ConstIterator &operator++() { + index_ = container_->NextOccupied(index_); + return *this; + } + HashSet32ConstIterator operator++(int) { + HashSet32ConstIterator tmp(*this); + ++(*this); + return tmp; + } + + uint32_t index() const { return index_; } + + bool operator<(const HashSet32ConstIterator &rhs) { return index_ < rhs.index_; } + bool operator<=(const HashSet32ConstIterator &rhs) { return index_ <= rhs.index_; } + bool operator>(const HashSet32ConstIterator &rhs) { return index_ > rhs.index_; } + bool operator>=(const HashSet32ConstIterator &rhs) { return index_ >= rhs.index_; } + bool operator==(const HashSet32ConstIterator &rhs) { return index_ == rhs.index_; } + bool operator!=(const HashSet32ConstIterator &rhs) { return index_ != rhs.index_; } + }; + + using iterator = HashSet32Iterator; + using const_iterator = HashSet32ConstIterator; + + iterator begin() { + for (uint32_t i = 0; i < capacity_; i++) { + if (ctrl_[i] & OccupiedBit) { + return iterator(this, i); + } + } + return end(); + } + const_iterator begin() const { return cbegin(); } + + const_iterator cbegin() const { + for (uint32_t i = 0; i < capacity_; i++) { + if (ctrl_[i] & OccupiedBit) { + return const_iterator(this, i); + } + } + return cend(); + } + + iterator end() { return iterator(this, capacity_); } + const_iterator end() const { return const_iterator(this, capacity_); } + const_iterator cend() const { return const_iterator(this, capacity_); } + + iterator iter_at(const uint32_t i) { return iterator(this, i); } + const_iterator citer_at(const uint32_t i) const { return const_iterator(this, i); } + + Node &at(const uint32_t index) { + assert((ctrl_[index] & OccupiedBit) && "Invalid index!"); + return nodes_[index]; + } + + const Node &at(const uint32_t index) const { + assert((ctrl_[index] & OccupiedBit) && "Invalid index!"); + return nodes_[index]; + } + + iterator erase(const iterator it) { + const uint32_t next = NextOccupied(it.index_); + + --size_; + ctrl_[it.index_] = HashMask; + nodes_[it.index_].key.~K(); + + return iter_at(next); + } + + private: + void CheckRealloc() { + if ((size_ + 1) > uint32_t(0.8f * capacity_)) { + ReserveRealloc(capacity_ * 2); + } + } + + void ReserveRealloc(const uint32_t desired_capacity) { + if (!capacity_ || capacity_ < desired_capacity) { + uint8_t *old_ctrl = ctrl_; + Node *old_nodes = nodes_; + uint32_t old_capacity = capacity_; + + if (!capacity_) { + capacity_ = 8; + } + while (capacity_ < desired_capacity) { + capacity_ *= 2; + } + size_ = 0; + + ctrl_ = this->allocate(mem_size(capacity_)); + if (!ctrl_) { + return; + } + nodes_ = reinterpret_cast(&ctrl_[ctrl_size(capacity_)]); + memset(ctrl_, 0, capacity_); + + for (uint32_t i = 0; i < old_capacity; ++i) { + if (old_ctrl[i] & OccupiedBit) { + InsertInternal(old_nodes[i].hash, std::move(old_nodes[i].key)); + } + } + + this->deallocate(old_ctrl, mem_size(old_capacity)); + } + } + + Node *InsertInternal(const uint32_t hash, const K &key) { + CheckRealloc(); + + uint32_t i = hash & (capacity_ - 1); + while (ctrl_[i] & OccupiedBit) { + i = (i + 1) & (capacity_ - 1); + } + + ++size_; + ctrl_[i] = OccupiedBit | (hash & HashMask); + + Node *ret = &nodes_[i]; + ret->hash = hash; + new (&ret->key) K(key); + + return ret; + } + + void InsertInternal(const uint32_t hash, K &&key) { + CheckRealloc(); + + uint32_t i = hash & (capacity_ - 1); + while (ctrl_[i] & OccupiedBit) { + i = (i + 1) & (capacity_ - 1); + } + + ++size_; + ctrl_[i] = OccupiedBit | (hash & HashMask); + + Node &ret = nodes_[i]; + ret.hash = hash; + new (&ret.key) K(std::forward(key)); + } + + uint32_t NextOccupied(uint32_t index) const { + assert((ctrl_[index] & OccupiedBit) && "Invalid index!"); + for (uint32_t i = index + 1; i < capacity_; ++i) { + if (ctrl_[i] & OccupiedBit) { + return i; + } + } + return capacity_; + } +}; +} // namespace Ray diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 746ce11c..1bb1de26 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(test_Ray main.cpp test_aux_channels.cpp test_freelist_alloc.cpp test_hashmap.cpp + test_hashset.cpp test_huffman.cpp test_inflate.cpp test_scene.h diff --git a/tests/main.cpp b/tests/main.cpp index da7e79f9..770e7595 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -13,6 +13,7 @@ void test_simd(); void test_hashmap(); +void test_hashset(); void test_huffman(); void test_inflate(); void test_scope_exit(); @@ -201,6 +202,7 @@ int main(int argc, char *argv[]) { test_simd(); puts(" ---------------"); test_freelist_alloc(); + test_hashset(); test_hashmap(); test_huffman(); test_inflate(); diff --git a/tests/test_hashmap.cpp b/tests/test_hashmap.cpp index ce5db4e2..3ecd7502 100644 --- a/tests/test_hashmap.cpp +++ b/tests/test_hashmap.cpp @@ -4,6 +4,23 @@ #include "../internal/HashMap32.h" +struct alignas(64) TestHashMapStruct { + uint32_t val; + + TestHashMapStruct(const uint32_t _val) : val(_val) { require((uintptr_t(this) % 64) == 0); } + TestHashMapStruct(const TestHashMapStruct &rhs) : val(rhs.val) { require((uintptr_t(this) % 64) == 0); } +}; + +template <> class Ray::Hash { + public: + uint32_t operator()(const TestHashMapStruct &s) const { return s.val; } +}; + +template <> class Ray::Equal { + public: + bool operator()(const TestHashMapStruct &k1, const TestHashMapStruct &k2) const { return k1.val == k2.val; } +}; + void test_hashmap() { using namespace Ray; @@ -64,6 +81,110 @@ void test_hashmap() { require(cont.Erase(144)); } } + + { // Initializer list + HashMap32 cont{{12, 12.0}, {42, 16.5}, {15, 11.15}, {10, 18.53}, {-42, -16.5}, {144, 916.0}}; + + require(!cont.Insert(12, 0.0)); + require(!cont.Insert(42, 0.0)); + require(!cont.Insert(15, 0.0)); + require(!cont.Insert(10, 0.0)); + require(!cont.Insert(-42, 0.0)); + require(!cont.Insert(144, 0.0)); + + cont[15] = 17.894; + cont[27] = -13.0; + + double *p_val = nullptr; + + p_val = cont.Find(12); + require(p_val && *p_val == 12.0); + + p_val = cont.Find(12); + require(p_val && *p_val == 12.0); + + p_val = cont.Find(42); + require(p_val && *p_val == 16.5); + + p_val = cont.Find(15); + require(p_val && *p_val == 17.894); + + p_val = cont.Find(10); + require(p_val && *p_val == 18.53); + + p_val = cont.Find(-42); + require(p_val && *p_val == -16.5); + + p_val = cont.Find(144); + require(p_val && *p_val == 916.0); + + p_val = cont.Find(27); + require(p_val && *p_val == -13.0); + + require(cont.Erase(12)); + require(cont.Erase(42)); + require(cont.Erase(15)); + require(cont.Erase(10)); + require(cont.Erase(-42)); + require(cont.Erase(144)); + } + + { // Alignment test + HashMap32 cont; + + for (int i = 0; i < 100; i++) { + require(cont.Insert(12, 12.0)); + require(cont.Insert(42, 16.5)); + require(cont.Insert(15, 11.15)); + require(cont.Insert(10, 18.53)); + require(cont.Insert(-42, -16.5)); + require(cont.Insert(144, 916.0)); + + require(!cont.Insert(12, 0.0)); + require(!cont.Insert(42, 0.0)); + require(!cont.Insert(15, 0.0)); + require(!cont.Insert(10, 0.0)); + require(!cont.Insert(-42, 0.0)); + require(!cont.Insert(144, 0.0)); + + cont[15] = 17.894; + cont[27] = -13.0; + + double *p_val = nullptr; + + p_val = cont.Find(12); + require(p_val && *p_val == 12.0); + + p_val = cont.Find(12); + require(p_val && *p_val == 12.0); + + p_val = cont.Find(42); + require(p_val && *p_val == 16.5); + + p_val = cont.Find(15); + require(p_val && *p_val == 17.894); + + p_val = cont.Find(10); + require(p_val && *p_val == 18.53); + + p_val = cont.Find(-42); + require(p_val && *p_val == -16.5); + + p_val = cont.Find(144); + require(p_val && *p_val == 916.0); + + p_val = cont.Find(27); + require(p_val && *p_val == -13.0); + + require(cont.Erase(12)); + require(cont.Erase(42)); + require(cont.Erase(15)); + require(cont.Erase(10)); + require(cont.Erase(-42)); + require(cont.Erase(144)); + } + } + { // Test with reallocation HashMap32 cont(16); @@ -82,6 +203,7 @@ void test_hashmap() { require(cont.size() == 0); } + { // Test iteration HashMap32 cont(16); diff --git a/tests/test_hashset.cpp b/tests/test_hashset.cpp new file mode 100644 index 00000000..bc22d60e --- /dev/null +++ b/tests/test_hashset.cpp @@ -0,0 +1,218 @@ +#include "test_common.h" + +#include + +#include "../internal/HashSet32.h" + +struct alignas(64) TestHashSetStruct { + uint32_t val; + + TestHashSetStruct(const uint32_t _val) : val(_val) { require((uintptr_t(this) % 64) == 0); } + TestHashSetStruct(const TestHashSetStruct &rhs) : val(rhs.val) { require((uintptr_t(this) % 64) == 0); } +}; + +template <> class Ray::Hash { + public: + uint32_t operator()(const TestHashSetStruct &s) const { return s.val; } +}; + +template <> class Ray::Equal { + public: + bool operator()(const TestHashSetStruct &k1, const TestHashSetStruct &k2) const { return k1.val == k2.val; } +}; + +void test_hashset() { + using namespace Ray; + + printf("Test hashset | "); + + { // Basic test + HashSet32 cont; + + for (int i = 0; i < 100; i++) { + require(cont.Insert(12)); + require(cont.Insert(42)); + require(cont.Insert(15)); + require(cont.Insert(10)); + require(cont.Insert(-42)); + require(cont.Insert(144)); + + require(!cont.Insert(12)); + require(!cont.Insert(42)); + require(!cont.Insert(15)); + require(!cont.Insert(10)); + require(!cont.Insert(-42)); + require(!cont.Insert(144)); + + int *p_val = nullptr; + + p_val = cont.Find(12); + require(p_val && *p_val == 12); + + p_val = cont.Find(12); + require(p_val && *p_val == 12); + + p_val = cont.Find(42); + require(p_val && *p_val == 42); + + p_val = cont.Find(15); + require(p_val && *p_val == 15); + + p_val = cont.Find(10); + require(p_val && *p_val == 10); + + p_val = cont.Find(-42); + require(p_val && *p_val == -42); + + p_val = cont.Find(144); + require(p_val && *p_val == 144); + + require(cont.Erase(12)); + require(cont.Erase(42)); + require(cont.Erase(15)); + require(cont.Erase(10)); + require(cont.Erase(-42)); + require(cont.Erase(144)); + } + } + + { // Initializer list + HashSet32 cont{{12, 42, 15, 10, -42, 144}}; + + require(!cont.Insert(12)); + require(!cont.Insert(42)); + require(!cont.Insert(15)); + require(!cont.Insert(10)); + require(!cont.Insert(-42)); + require(!cont.Insert(144)); + + int *p_val = nullptr; + + p_val = cont.Find(12); + require(p_val && *p_val == 12); + + p_val = cont.Find(12); + require(p_val && *p_val == 12); + + p_val = cont.Find(42); + require(p_val && *p_val == 42); + + p_val = cont.Find(15); + require(p_val && *p_val == 15); + + p_val = cont.Find(10); + require(p_val && *p_val == 10); + + p_val = cont.Find(-42); + require(p_val && *p_val == -42); + + p_val = cont.Find(144); + require(p_val && *p_val == 144); + + require(cont.Erase(12)); + require(cont.Erase(42)); + require(cont.Erase(15)); + require(cont.Erase(10)); + require(cont.Erase(-42)); + require(cont.Erase(144)); + } + + { // Alignemnt test + HashSet32 cont; + + for (int i = 0; i < 100; i++) { + require(cont.Insert(12)); + require(cont.Insert(42)); + require(cont.Insert(15)); + require(cont.Insert(10)); + require(cont.Insert(-42)); + require(cont.Insert(144)); + + TestHashSetStruct *p_val = nullptr; + + p_val = cont.Find(12); + require(p_val && p_val->val == 12); + + p_val = cont.Find(12); + require(p_val && p_val->val == 12); + + p_val = cont.Find(42); + require(p_val && p_val->val == 42); + + p_val = cont.Find(15); + require(p_val && p_val->val == 15); + + p_val = cont.Find(10); + require(p_val && p_val->val == 10); + + p_val = cont.Find(-42); + require(p_val && p_val->val == -42); + + p_val = cont.Find(144); + require(p_val && p_val->val == 144); + + require(cont.Erase(12)); + require(cont.Erase(42)); + require(cont.Erase(15)); + require(cont.Erase(10)); + require(cont.Erase(-42)); + require(cont.Erase(144)); + } + } + + { // Test with reallocation + HashSet32 cont(16); + + for (int i = 0; i < 100000; i++) { + std::string key = std::to_string(i); + cont.Insert(std::move(key)); + } + + require(cont.size() == 100000); + + for (int i = 0; i < 100000; i++) { + std::string key = std::to_string(i); + std::string *_key = cont.Find(key); + require(_key && *_key == key); + require(cont.Erase(key)); + } + + require(cont.size() == 0); + } + + { // Test iteration + HashSet32 cont(16); + + for (int i = 0; i < 100000; i++) { + std::string key = std::to_string(i); + cont.Insert(std::move(key)); + } + + require(cont.size() == 100000); + + { // const iterator + int values_count = 0; + for (auto it = cont.cbegin(); it != cont.cend(); ++it) { + // require(it->key == std::to_string(it->val)); + values_count++; + } + + require(values_count == 100000); + } + + { // normal iterator + int values_count = 0; + for (auto it = cont.begin(); it != cont.end(); ++it) { + // require(it->key == std::to_string(it->val)); + values_count++; + } + + require(values_count == 100000); + } + } + + { // Initializer list + } + + printf("OK\n"); +} \ No newline at end of file