Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // trivial_bookshelf.h
- #include <cstdlib>
- #include <cstring>
- #include <cassert>
- #include <type_traits>
- #include <new>
- #include <limits>
- #include <memory>
- // target feature: iterator returned by "insert" is guaranteed to remain valid until "erase"d by user
- // though the object itself may change address
- // insert -- O(1) (O(N) on realloc, but pretty soft)
- // erase -- O(1)
- // access -- O(1)
- // never calls T constructors and destructors, "insert" is done by std::memcpy
- // unused memory is always(!) zeroed
- template<typename T, typename uint = std::size_t>
- class trivial_bookshelf
- {
- //static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
- T *data_ptr;
- uint *available_idx;
- uint ai_size;
- uint ai_capacity;
- static constexpr double realloc_factor = 1.5;
- void reallocate(uint const) noexcept;
- public:
- ~trivial_bookshelf();
- trivial_bookshelf() noexcept;
- trivial_bookshelf(trivial_bookshelf const &) noexcept;
- trivial_bookshelf(trivial_bookshelf &&) noexcept;
- T &operator[](uint const i) noexcept;
- T const &operator[](uint const i) const noexcept;
- T * data() noexcept;
- T const * data() const noexcept;
- uint insert(T const &item) noexcept;
- void erase(uint const idx) noexcept;
- uint size() const noexcept;
- uint capacity() const noexcept;
- void clear() noexcept;
- void reserve(uint const n) noexcept;
- };
- template<typename T, typename uint>
- trivial_bookshelf<T, uint>::trivial_bookshelf() noexcept
- : data_ptr(nullptr)
- , available_idx(nullptr)
- , ai_size(0)
- , ai_capacity(0)
- {}
- template<typename T, typename uint>
- trivial_bookshelf<T, uint>::trivial_bookshelf(trivial_bookshelf const &b) noexcept
- : data_ptr( static_cast<T * >(std::malloc(b.ai_capacity * sizeof(T))))
- , available_idx(static_cast<uint *>(std::malloc(b.ai_capacity * sizeof(uint))))
- , ai_size(b.ai_size)
- , ai_capacity(b.ai_capacity)
- {
- std::memcpy(data_ptr, b.data_ptr, b.ai_capacity * sizeof(T));
- std::memcpy(available_idx, b.available_idx, b.ai_capacity * sizeof(uint));
- }
- template<typename T, typename uint>
- trivial_bookshelf<T, uint>::trivial_bookshelf(trivial_bookshelf &&b) noexcept
- : data_ptr(b.data_ptr)
- , available_idx(b.available_idx)
- , ai_size(b.ai_size)
- , ai_capacity(b.ai_capacity)
- {
- b.data_ptr = nullptr;
- b.available_idx = nullptr;
- b.ai_size = 0;
- b.ai_capacity = 0;
- }
- template<typename T, typename uint>
- void trivial_bookshelf<T, uint>::reallocate(uint const cap) noexcept
- {
- if(cap <= ai_capacity)
- return;
- data_ptr = static_cast<T * >(std::realloc(data_ptr, cap * sizeof(T)));
- available_idx = static_cast<uint *>(std::realloc(available_idx, cap * sizeof(uint)));
- assert(data_ptr != nullptr);
- assert(available_idx != nullptr);
- std::memset(data_ptr + ai_capacity, 0, (cap - ai_capacity) * sizeof(T));
- for(uint i = ai_capacity; i < cap; i++)
- available_idx[i] = i;
- ai_capacity = cap;
- }
- template<typename T, typename uint>
- trivial_bookshelf<T, uint>::~trivial_bookshelf()
- {
- std::free(data_ptr);
- std::free(available_idx);
- data_ptr = nullptr;
- available_idx = nullptr;
- ai_size = 0;
- ai_capacity = 0;
- }
- template<typename T, typename uint>
- T &trivial_bookshelf<T, uint>::operator[](uint const i) noexcept
- {
- assert(i < ai_capacity);
- return data_ptr[i];
- }
- template<typename T, typename uint>
- T const &trivial_bookshelf<T, uint>::operator[](uint const i) const noexcept
- {
- assert(i < ai_capacity);
- return data_ptr[i];
- }
- template<typename T, typename uint>
- uint trivial_bookshelf<T, uint>::insert(T const &item) noexcept
- {
- if(ai_size == ai_capacity)
- reallocate(ai_capacity == 0 ? 3 : ai_capacity * realloc_factor);
- std::memcpy(data_ptr + available_idx[ai_size], &item, sizeof(T));
- return available_idx[ai_size++];
- }
- template<typename T, typename uint>
- void trivial_bookshelf<T, uint>::erase(uint const idx) noexcept
- {
- assert(ai_size != 0);
- assert(idx < ai_capacity);
- std::memset(data_ptr + idx, 0, sizeof(T));
- available_idx[--ai_size] = idx;
- }
- template<typename T, typename uint>
- T *trivial_bookshelf<T, uint>::data() noexcept
- {
- return data_ptr;
- }
- template<typename T, typename uint>
- T const *trivial_bookshelf<T, uint>::data() const noexcept
- {
- return data_ptr;
- }
- template<typename T, typename uint>
- uint trivial_bookshelf<T, uint>::size() const noexcept
- {
- return ai_size;
- }
- template<typename T, typename uint>
- uint trivial_bookshelf<T, uint>::capacity() const noexcept
- {
- return ai_capacity;
- }
- template<typename T, typename uint>
- void trivial_bookshelf<T, uint>::clear() noexcept
- {
- ai_size = 0;
- std::memset(data_ptr, 0, ai_capacity * sizeof(T));
- // this is optional, but helpful
- for(uint i = 0; i < ai_capacity; i++)
- available_idx[i] = i;
- }
- template<typename T, typename uint>
- void trivial_bookshelf<T, uint>::reserve(uint const n) noexcept
- {
- reallocate(n);
- }
- //////////////////////////////////////////////////////////////////
- #include <cstdint> // std::uint32_t
- #include <functional> // std::hash
- template
- <
- typename Key,
- typename Value,
- typename Hash = std::hash<Key>,
- typename KeyEqual = std::equal_to<Key>
- >
- class trivial_hash_map
- {
- static_assert(std::is_trivially_copyable<Key >::value, "Key type must be trivially copyable");
- static_assert(std::is_trivially_copyable<Value>::value, "Value type must be trivially copyable");
- using idx_t = std::uint32_t;
- static constexpr double load_factor = 0.9;
- // initialized with zeroes
- struct node
- {
- struct pair
- {
- Key first;
- Value second;
- } data;
- struct node_state
- {
- idx_t next;
- bool active;
- bool has_child;
- } state;
- };
- node *nodes_ptr;
- idx_t nodes_size;
- idx_t nodes_cap;
- trivial_bookshelf<node, idx_t> collisions;
- node *next_node(node * const ptr) noexcept {return &collisions[ptr->state.next];}
- node const *next_node(node const * const ptr) const noexcept {return &collisions[ptr->state.next];}
- idx_t hash_idx(Key const &key) const noexcept {return Hash{}(key) % nodes_cap;}
- public:
- ~trivial_hash_map();
- trivial_hash_map() noexcept;
- trivial_hash_map(idx_t const capacity) noexcept;
- trivial_hash_map(trivial_hash_map<Key, Value, Hash, KeyEqual> &&) noexcept;
- trivial_hash_map<Key, Value, Hash, KeyEqual> &operator=(trivial_hash_map<Key, Value, Hash, KeyEqual> &&) noexcept;
- struct iterator
- {
- node *ptr;
- node * const end_ptr;
- node * const begin_ptr;
- node * const total_end_ptr;
- iterator & operator++() noexcept;
- typename node::pair &operator* () const noexcept;
- typename node::pair *operator->() const noexcept;
- bool operator!=(iterator const &) const noexcept;
- bool operator==(iterator const &) const noexcept;
- };
- struct const_iterator
- {
- node const *ptr;
- node const * const end_ptr;
- node const * const begin_ptr;
- node const * const total_end_ptr;
- const_iterator & operator++() noexcept;
- typename node::pair const &operator* () const noexcept;
- typename node::pair const *operator->() const noexcept;
- bool operator!=(const_iterator const &) const noexcept;
- bool operator==(const_iterator const &) const noexcept;
- };
- iterator begin() noexcept;
- iterator end() noexcept;
- const_iterator cbegin() const noexcept;
- const_iterator cend() const noexcept;
- idx_t size() const noexcept {return nodes_size;}
- void rehash(idx_t const new_capacity) noexcept;
- iterator emplace(Key const &, Value const &) noexcept;
- void erase(iterator) noexcept;
- void erase(Key const &key) noexcept;
- iterator find(Key const &) noexcept;
- const_iterator find(Key const &) const noexcept;
- private:
- iterator iter(node *ptr) noexcept;
- const_iterator const_iter(node const *ptr) const noexcept;
- };
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- trivial_hash_map<Key, Value, Hash, KeyEqual>::~trivial_hash_map()
- {
- std::free(nodes_ptr);
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- trivial_hash_map<Key, Value, Hash, KeyEqual>::trivial_hash_map() noexcept
- : nodes_ptr(nullptr)
- , nodes_size(0)
- , nodes_cap(0)
- {}
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- trivial_hash_map<Key, Value, Hash, KeyEqual>::trivial_hash_map(idx_t const capacity) noexcept
- : nodes_ptr(static_cast<node *>(std::calloc(capacity, sizeof(node))))
- , nodes_size(0)
- , nodes_cap(capacity)
- {}
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- trivial_hash_map<Key, Value, Hash, KeyEqual>::trivial_hash_map(trivial_hash_map<Key, Value, Hash, KeyEqual> &&m) noexcept
- : nodes_ptr(m.nodes_ptr)
- , nodes_size(m.nodes_size)
- , nodes_cap(m.nodes_cap)
- , collisions(std::move(m.collisions))
- {
- m.nodes_ptr = nullptr;
- m.nodes_size = 0;
- m.nodes_cap = 0;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- trivial_hash_map<Key, Value, Hash, KeyEqual> &
- trivial_hash_map<Key, Value, Hash, KeyEqual>::operator=(trivial_hash_map<Key, Value, Hash, KeyEqual> &&m) noexcept
- {
- if(this == &m)
- return *this;
- this->~trivial_hash_map();
- new (this) trivial_hash_map(std::move(m));
- return *this;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::iter
- (trivial_hash_map<Key, Value, Hash, KeyEqual>::node *ptr) noexcept
- {
- return iterator
- {
- ptr,
- nodes_ptr + nodes_cap,
- collisions.data(),
- collisions.data() + collisions.capacity()
- };
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iter
- (trivial_hash_map<Key, Value, Hash, KeyEqual>::node const *ptr) const noexcept
- {
- return const_iterator
- {
- ptr,
- nodes_ptr + nodes_cap,
- collisions.data(),
- collisions.data() + collisions.capacity()
- };
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator &
- trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator::operator++() noexcept
- {
- while(1)
- {
- if(ptr + 1 != end_ptr)
- ptr++;
- else
- ptr = begin_ptr;
- if(ptr == total_end_ptr || ptr->state.active)
- break;
- }
- return *this;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::node::pair &
- trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator::operator*() const noexcept
- {
- return ptr->data;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::node::pair *
- trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator::operator->() const noexcept
- {
- return &ptr->data;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- bool trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator::operator!=(iterator const &it) const noexcept
- {
- return ptr != it.ptr;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- bool trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator::operator==(iterator const &it) const noexcept
- {
- return ptr == it.ptr;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::begin() noexcept
- {
- iterator it = iter(nodes_ptr);
- while(1)
- {
- if(it.ptr == it.total_end_ptr || it.ptr->state.active)
- break;
- if(it.ptr != it.end_ptr)
- it.ptr++;
- if(it.ptr == it.end_ptr)
- it.ptr = it.begin_ptr;
- }
- return it;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::end() noexcept
- {
- return iter(collisions.data() + collisions.capacity());
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator &
- trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator::operator++() noexcept
- {
- while(1)
- {
- if(ptr + 1 != end_ptr)
- ptr++;
- else
- ptr = begin_ptr;
- if(ptr == total_end_ptr || ptr->state.active)
- break;
- }
- return *this;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::node::pair const &
- trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator::operator*() const noexcept
- {
- return ptr->data;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::node::pair const *
- trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator::operator->() const noexcept
- {
- return &ptr->data;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- bool trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator::operator!=(const_iterator const &it) const noexcept
- {
- return ptr != it.ptr;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- bool trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator::operator==(const_iterator const &it) const noexcept
- {
- return ptr == it.ptr;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::cbegin() const noexcept
- {
- iterator it = const_iter(nodes_ptr);
- while(1)
- {
- if(it.ptr == it.total_end_ptr || it.ptr->state.active)
- break;
- if(it.ptr != it.end_ptr)
- it.ptr++;
- if(it.ptr == it.end_ptr)
- it.ptr = it.begin_ptr;
- }
- return it;
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::cend() const noexcept
- {
- return const_iter(collisions.data() + collisions.capacity());
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- void trivial_hash_map<Key, Value, Hash, KeyEqual>::rehash(idx_t const new_cap) noexcept
- {
- trivial_hash_map<Key, Value, Hash, KeyEqual> new_map(new_cap);
- for(auto const &pair : *this)
- new_map.emplace(pair.first, pair.second);
- *this = std::move(new_map);
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::emplace(Key const &key, Value const &value) noexcept
- {
- if(nodes_size >= nodes_cap * load_factor)
- rehash(nodes_cap * 2 + 7);
- KeyEqual const equal;
- idx_t const i = hash_idx(key);
- node *node_ptr = nodes_ptr + i;
- if(!node_ptr->state.active)
- {
- node_ptr->data = typename node::pair{key, value};
- node_ptr->state = typename node::node_state{0, true, false};
- nodes_size++;
- return iter(node_ptr);
- }
- if(equal(node_ptr->data.first, key))
- return iter(node_ptr);
- if(!node_ptr->state.has_child)
- {
- nodes_size++;
- node_ptr->state = {collisions.insert(node{key, value, 0, true, false}), true, true};
- return iter(next_node(node_ptr));
- }
- idx_t child_idx = node_ptr->state.next;
- while(1)
- {
- node &curr_node = collisions[child_idx];
- assert(hash_idx(curr_node.data.first) == i);
- if(equal(curr_node.data.first, key))
- return iter(&curr_node);
- if(!curr_node.state.has_child)
- {
- nodes_size++;
- // insert may invalidate pointers
- idx_t const new_idx = collisions.insert(node{key, value, 0, true, false});
- collisions[child_idx].state = {new_idx, true, true};
- return iter(&collisions[new_idx]);
- }
- else
- child_idx = curr_node.state.next;
- }
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::find(Key const &key) noexcept
- {
- KeyEqual const equal;
- idx_t const i = hash_idx(key);
- node *node_ptr = nodes_ptr + i;
- if(!node_ptr->state.active)
- return end();
- while(1)
- {
- if(equal(node_ptr->data.first, key))
- return iter(node_ptr);
- if(node_ptr->state.has_child)
- node_ptr = next_node(node_ptr);
- else
- return end();
- }
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- typename trivial_hash_map<Key, Value, Hash, KeyEqual>::const_iterator
- trivial_hash_map<Key, Value, Hash, KeyEqual>::find(Key const &key) const noexcept
- {
- KeyEqual const equal;
- idx_t const i = hash_idx(key);
- node const *node_ptr = nodes_ptr + i;
- if(!node_ptr->state.active)
- return cend();
- while(1)
- {
- if(equal(node_ptr->data.first, key))
- return const_iter(node_ptr);
- if(node_ptr->state.has_child)
- node_ptr = next_node(node_ptr);
- else
- return cend();
- }
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- void trivial_hash_map<Key, Value, Hash, KeyEqual>::erase(Key const &key) noexcept
- {
- KeyEqual const equal;
- idx_t const i = hash_idx(key);
- node *prev_node = nullptr;
- node *node_ptr = nodes_ptr + i;
- if(!node_ptr->state.active)
- return;
- while(!equal(node_ptr->data.first, key))
- {
- if(!node_ptr->state.has_child)
- return;
- prev_node = node_ptr;
- node_ptr = next_node(node_ptr);
- }
- // from here node_ptr->data.first == key
- nodes_size--;
- if(!node_ptr->state.has_child)
- {
- if(prev_node != nullptr)
- {
- collisions.erase(prev_node->state.next);
- prev_node->state = typename node::node_state{0, true, false};
- }
- else
- std::memset(node_ptr, 0, sizeof(typename node::pair) + sizeof(typename node::node_state));
- }
- else
- {
- idx_t const child_idx = node_ptr->state.next;
- node const &child = collisions[child_idx];
- node_ptr->data = child.data;
- node_ptr->state = child.state;
- collisions.erase(child_idx);
- }
- }
- template<typename Key, typename Value, typename Hash, typename KeyEqual>
- void trivial_hash_map<Key, Value, Hash, KeyEqual>::erase(iterator it) noexcept
- {
- if(it.ptr->state.has_child)
- {
- idx_t const child_idx = it.ptr->state.next;
- node const &child = collisions[child_idx];
- it.ptr->data = child.data;
- it.ptr->state = child.state;
- collisions.erase(child_idx);
- nodes_size--;
- }
- else
- erase(it.ptr->data.first);
- }
- #include <iostream>
- #include <chrono>
- #include <unordered_map>
- void printExecutionTime(std::string const & name, std::function<void ()> const & f) {
- using namespace std::chrono;
- auto start = system_clock::now();
- f();
- auto end = system_clock::now();
- auto ms = duration_cast<std::chrono::milliseconds>(end - start);
- std::cout << name << " took " << ms.count() << " ms" << '\n';
- }
- size_t const N = 8 * 1024 * 1024;
- class Buffer {
- size_t capacity = 1024 * N;
- void * data = malloc(capacity);
- size_t offset = 0;
- public:
- void * allocate(std::size_t size) {
- void * result = data + offset;
- offset += size;
- return result;
- }
- ~Buffer() {
- free(data);
- }
- };
- template <class T>
- struct TvoyaMamkaAllocator {
- typedef T value_type;
- std::shared_ptr<Buffer> buffer{new Buffer};
- TvoyaMamkaAllocator() = default;
- template <class U> constexpr TvoyaMamkaAllocator(const TvoyaMamkaAllocator<U>&o) noexcept
- : buffer(o.buffer) {}
- [[nodiscard]] T* allocate(std::size_t n) {
- return static_cast<T*>(buffer->allocate(sizeof(T) * n));
- }
- void deallocate(T* p, std::size_t) noexcept { }
- };
- using CustomAllocator = TvoyaMamkaAllocator<std::pair<const int, int>>;
- int main()
- {
- auto const testEmplace = [](auto& map){
- map.rehash(N);
- for(int i = 0; i < N; i++)
- map.emplace(i, 0);
- };
- auto const testErase = [](auto& map){
- for(int i = 0; i < N; i++)
- map.erase(i);
- };
- trivial_hash_map<int, int> bicycle;
- std::unordered_map<int, int> unordered_map;
- std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, CustomAllocator> custom_allocator_map;
- printExecutionTime("emplace bicycle", [&](){ testEmplace(bicycle); });
- printExecutionTime("emplace unordered_map", [&](){ testEmplace(unordered_map); });
- printExecutionTime("emplace custom_allocator_map", [&](){ testEmplace(custom_allocator_map); });
- printExecutionTime("erase bicycle", [&](){ testErase(bicycle); });
- printExecutionTime("erase unordered_map", [&](){ testErase(unordered_map); });
- printExecutionTime("erase custom_allocator_map", [&](){ testErase(custom_allocator_map); });
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement