From 7d395ac826bac3224817ad5787b25b380f79dfdf Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 29 Apr 2014 04:52:29 +0400 Subject: [PATCH] dbms: unified hash tables: development [#METR-2944]. --- .../DB/Interpreters/ClearableHashMap.h | 224 ------- dbms/include/DB/Interpreters/HashMap.h | 614 ------------------ dbms/include/DB/Interpreters/HashSet.h | 510 --------------- 3 files changed, 1348 deletions(-) delete mode 100644 dbms/include/DB/Interpreters/ClearableHashMap.h delete mode 100644 dbms/include/DB/Interpreters/HashMap.h delete mode 100644 dbms/include/DB/Interpreters/HashSet.h diff --git a/dbms/include/DB/Interpreters/ClearableHashMap.h b/dbms/include/DB/Interpreters/ClearableHashMap.h deleted file mode 100644 index 963511a7cc8..00000000000 --- a/dbms/include/DB/Interpreters/ClearableHashMap.h +++ /dev/null @@ -1,224 +0,0 @@ -#pragma once - -#include - -#include - -#include - -#include - -#include - -#include - -#include -#include -#include - - -namespace DB -{ - - -/** Хеш-таблица, позволяющая очищать таблицу за O(1). - * Еще более простая, чем HashMap: Key и Mapped должны быть POD-типами. - * - * Вместо этого класса можно было бы просто использовать в HashMap в качестве ключа пару <версия, ключ>, - * но тогда таблица накапливала бы все ключи, которые в нее когда-либо складывали, и неоправданно росла. - * Этот класс идет на шаг дальше и считает ключи со старой версией пустыми местами в хеш-таблице. - */ - -template -< - typename Key, - typename Mapped, - typename Hash = default_hash, - typename GrowthTraits = default_growth_traits, - typename Allocator = HashTableAllocator -> -class ClearableHashMap : private boost::noncopyable, private Hash, private Allocator /// empty base optimization -{ -private: - struct Value - { - Key key; - UInt32 version; - Mapped mapped; - }; - typedef size_t HashValue; - - size_t m_size; /// Количество элементов - Value * buf; /// Кусок памяти для всех элементов кроме элемента с ключём 0. - UInt32 version; - UInt8 size_degree; /// Размер таблицы в виде степени двух - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - mutable size_t collisions; -#endif - - inline size_t hash(const Key & x) const { return Hash::operator()(x); } - inline size_t buf_size() const { return 1 << size_degree; } - inline size_t buf_size_bytes() const { return buf_size() * sizeof(Value); } - inline size_t max_fill() const { return 1 << (size_degree - 1); } - inline size_t mask() const { return buf_size() - 1; } - inline size_t place(HashValue x) const { return x & mask(); } - - - /// Увеличить размер буфера в 2 ^ N раз - void resize() - { -#ifdef DBMS_HASH_MAP_DEBUG_RESIZES - Stopwatch watch; -#endif - - size_t old_size = buf_size(); - size_t old_size_bytes = buf_size_bytes(); - - size_degree += size_degree >= GrowthTraits::GROWTH_CHANGE_THRESHOLD - ? 1 - : GrowthTraits::FAST_GROWTH_DEGREE; - - /// Расширим пространство. - buf = reinterpret_cast(Allocator::realloc(buf, old_size_bytes, buf_size_bytes())); - - /** Теперь некоторые элементы может потребоваться переместить на новое место. - * Элемент может остаться на месте, или переместиться в новое место "справа", - * или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа". - */ - size_t i = 0; - for (; i < old_size; ++i) - if (buf[i].version == version) - reinsert(buf[i]); - - /** Также имеется особый случай: - * если элемент должен был быть в конце старого буфера, [ x] - * но находится в начале из-за цепочки разрешения коллизий, [o x] - * то после ресайза, он сначала снова окажется не на своём месте, [ xo ] - * и для того, чтобы перенести его куда надо, - * надо будет после переноса всех элементов из старой половинки [ o x ] - * обработать ещё хвостик из цепочки разрешения коллизий сразу после неё [ o x ] - */ - for (; buf[i].version == version; ++i) - reinsert(buf[i]); - -#ifdef DBMS_HASH_MAP_DEBUG_RESIZES - watch.stop(); - std::cerr << std::fixed << std::setprecision(3) - << "Resize from " << old_size << " to " << buf_size() << " took " << watch.elapsedSeconds() << " sec." - << std::endl; -#endif - } - - - /** Вставить в новый буфер значение, которое было в старом буфере. - * Используется при увеличении размера буфера. - */ - void reinsert(Value & x) - { - size_t place_value = place(hash(x.key)); - - /// Если элемент на своём месте. - if (&x == &buf[place_value]) - return; - - /// Вычисление нового места, с учётом цепочки разрешения коллизий. - while (buf[place_value].version == version && x.key != buf[place_value].key) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - /// Если элемент остался на своём месте в старой цепочке разрешения коллизий. - if (buf[place_value].version == version && x.key == buf[place_value].key) - return; - - /// Копирование на новое место и зануление старого. - memcpy(&buf[place_value], &x, sizeof(x)); - x.version = 0; - - /// Потом на старое место могут переместиться элементы, которые раньше были в коллизии с этим. - } - - -public: - typedef Key key_type; - typedef Mapped mapped_type; - typedef Value value_type; - - - ClearableHashMap() : - m_size(0), - version(1), - size_degree(GrowthTraits::INITIAL_SIZE_DEGREE) - { - buf = reinterpret_cast(Allocator::alloc(buf_size_bytes())); - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - collisions = 0; -#endif - } - - ~ClearableHashMap() - { - Allocator::free(buf, buf_size_bytes()); - } - - - size_t size() const - { - return m_size; - } - - bool empty() const - { - return 0 == m_size; - } - - Mapped & operator[](Key x) - { - size_t place_value = place(hash(x)); - while (buf[place_value].version == version && buf[place_value].key != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - if (buf[place_value].version == version) - return buf[place_value].mapped; - - buf[place_value].key = x; - buf[place_value].mapped = Mapped(); - buf[place_value].version = version; - ++m_size; - - if (unlikely(m_size > max_fill())) - { - resize(); - return (*this)[x]; - } - - return buf[place_value].mapped; - } - - void clear() - { - ++version; - m_size = 0; - } - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - size_t getCollisions() const - { - return collisions; - } -#endif -}; - -} diff --git a/dbms/include/DB/Interpreters/HashMap.h b/dbms/include/DB/Interpreters/HashMap.h deleted file mode 100644 index 1f5b490cef6..00000000000 --- a/dbms/include/DB/Interpreters/HashMap.h +++ /dev/null @@ -1,614 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include - -#include - -#include - -#include - -#include -#include -#include - -#include - -#ifdef DBMS_HASH_MAP_DEBUG_RESIZES - #include - #include - #include -#endif - - -namespace DB -{ - - -/** Очень простая хэш-таблица. Предназначена для быстрой агрегации. Есть только необходимый минимум возможностей. - * Требования: - * - Key и Mapped - position independent типы (для перемещения значений которых достаточно сделать memcpy). - * - * Желательно, чтобы Key был числом, или маленьким агрегатом (типа UInt128). - * - * Сценарий работы: - * - вставлять в хэш-таблицу значения; - * - проитерироваться по имеющимся в ней значениям. - * - * Open addressing. - * Linear probing (подходит, если хэш функция хорошая!). - * Значение с нулевым ключём хранится отдельно. - * Удаления элементов нет. - */ - - -/** Хэш функции, которые лучше чем тривиальная функция std::hash. - * (при агрегации по идентификатору посетителя, прирост производительности более чем в 5 раз) - */ -template struct default_hash; - -template -inline size_t default_hash_64(T key) -{ - union - { - T in; - UInt64 out; - } u; - u.out = 0; - u.in = key; - return intHash32<0>(u.out); -} - -#define DEFAULT_HASH_64(T) \ -template <> struct default_hash\ -{\ - size_t operator() (T key) const\ - {\ - return default_hash_64(key);\ - }\ -}; - -DEFAULT_HASH_64(UInt8) -DEFAULT_HASH_64(UInt16) -DEFAULT_HASH_64(UInt32) -DEFAULT_HASH_64(UInt64) -DEFAULT_HASH_64(Int8) -DEFAULT_HASH_64(Int16) -DEFAULT_HASH_64(Int32) -DEFAULT_HASH_64(Int64) -DEFAULT_HASH_64(Float32) -DEFAULT_HASH_64(Float64) - -#undef DEFAULT_HASH_64 - - -/** Способ проверить, что ключ нулевой, - * а также способ установить значение ключа в ноль. - * При этом, нулевой ключ всё-равно должен быть представлен только нулевыми байтами - * (кроме, возможно, мусора из-за выравнивания). - */ -template struct default_zero_traits -{ - static inline bool check(T x) { return 0 == x; } - static inline void set(T & x) { x = 0; } -}; - - -/** Описание, как хэш-таблица будет расти. - */ -struct default_growth_traits -{ - /** Изначально выделить кусок памяти для 64K элементов. - * Уменьшите значение для лучшей кэш-локальности в случае маленького количества уникальных ключей. - */ - static const int INITIAL_SIZE_DEGREE = 16; - - /** Степень роста хэш таблицы, пока не превышен порог размера. (В 4 раза.) - */ - static const int FAST_GROWTH_DEGREE = 2; - - /** Порог размера, после которого степень роста уменьшается (до роста в 2 раза) - 8 миллионов элементов. - * После этого порога, максимально возможный оверхед по памяти будет всего лишь в 4, а не в 8 раз. - */ - static const int GROWTH_CHANGE_THRESHOLD = 23; -}; - - -template -< - typename Key, - typename Mapped, - typename Hash = default_hash, - typename ZeroTraits = default_zero_traits, - typename GrowthTraits = default_growth_traits, - typename Allocator = HashTableAllocator -> -class HashMap : private boost::noncopyable, private Hash, private Allocator /// empty base optimization -{ -private: - friend class const_iterator; - friend class iterator; - - typedef std::pair Value; /// Без const Key для простоты. - typedef size_t HashValue; - typedef HashMap Self; - - size_t m_size; /// Количество элементов - Value * buf; /// Кусок памяти для всех элементов кроме элемента с ключём 0. - UInt8 size_degree; /// Размер таблицы в виде степени двух - bool has_zero; /// Хэш-таблица содержит элемент со значением ключа = 0. - - char zero_value_storage[sizeof(Value)]; /// Кусок памяти для элемента с ключём 0. - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - mutable size_t collisions; -#endif - - inline size_t hash(const Key & x) const { return Hash::operator()(x); } - inline size_t buf_size() const { return 1 << size_degree; } - inline size_t buf_size_bytes() const { return buf_size() * sizeof(Value); } - inline size_t max_fill() const { return 1 << (size_degree - 1); } - inline size_t mask() const { return buf_size() - 1; } - inline size_t place(HashValue x) const { return x & mask(); } - - inline Value * zero_value() { return reinterpret_cast(zero_value_storage); } - - - /// Увеличить размер буфера в 2 ^ N раз - void resize() - { -#ifdef DBMS_HASH_MAP_DEBUG_RESIZES - Stopwatch watch; -#endif - - size_t old_size = buf_size(); - size_t old_size_bytes = buf_size_bytes(); - - size_degree += size_degree >= GrowthTraits::GROWTH_CHANGE_THRESHOLD - ? 1 - : GrowthTraits::FAST_GROWTH_DEGREE; - - /// Расширим пространство. - buf = reinterpret_cast(Allocator::realloc(buf, old_size_bytes, buf_size_bytes())); - - /** Теперь некоторые элементы может потребоваться переместить на новое место. - * Элемент может остаться на месте, или переместиться в новое место "справа", - * или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа". - */ - size_t i = 0; - for (; i < old_size; ++i) - if (!ZeroTraits::check(buf[i].first)) - reinsert(buf[i]); - - /** Также имеется особый случай: - * если элемент должен был быть в конце старого буфера, [ x] - * но находится в начале из-за цепочки разрешения коллизий, [o x] - * то после ресайза, он сначала снова окажется не на своём месте, [ xo ] - * и для того, чтобы перенести его куда надо, - * надо будет после переноса всех элементов из старой половинки [ o x ] - * обработать ещё хвостик из цепочки разрешения коллизий сразу после неё [ o x ] - */ - for (; !ZeroTraits::check(buf[i].first); ++i) - reinsert(buf[i]); - -#ifdef DBMS_HASH_MAP_DEBUG_RESIZES - watch.stop(); - std::cerr << std::fixed << std::setprecision(3) - << "Resize from " << old_size << " to " << buf_size() << " took " << watch.elapsedSeconds() << " sec." - << std::endl; -#endif - } - - - /** Вставить в новый буфер значение, которое было в старом буфере. - * Используется при увеличении размера буфера. - */ - void reinsert(Value & x) - { - size_t place_value = place(hash(x.first)); - - /// Если элемент на своём месте. - if (&x == &buf[place_value]) - return; - - /// Вычисление нового места, с учётом цепочки разрешения коллизий. - while (!ZeroTraits::check(buf[place_value].first) && x.first != buf[place_value].first) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - /// Если элемент остался на своём месте в старой цепочке разрешения коллизий. - if (!ZeroTraits::check(buf[place_value].first) && x.first == buf[place_value].first) - return; - - /// Копирование на новое место и зануление старого. - memcpy(&buf[place_value], &x, sizeof(x)); - ZeroTraits::set(x.first); - - /// Потом на старое место могут переместиться элементы, которые раньше были в коллизии с этим. - } - - -public: - typedef Key key_type; - typedef Mapped mapped_type; - typedef Value value_type; - - - HashMap() : - m_size(0), - size_degree(GrowthTraits::INITIAL_SIZE_DEGREE), - has_zero(false) - { - ZeroTraits::set(zero_value()->first); - buf = reinterpret_cast(Allocator::alloc(buf_size_bytes())); - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - collisions = 0; -#endif - } - - ~HashMap() - { - if (!__has_trivial_destructor(Key) || !__has_trivial_destructor(Mapped)) - for (iterator it = begin(); it != end(); ++it) - it->~Value(); - - Allocator::free(buf, buf_size_bytes()); - } - - - class iterator - { - Self * container; - Value * ptr; - - friend class HashMap; - - iterator(Self * container_, Value * ptr_) : container(container_), ptr(ptr_) {} - - public: - iterator() {} - - bool operator== (const iterator & rhs) const { return ptr == rhs.ptr; } - bool operator!= (const iterator & rhs) const { return ptr != rhs.ptr; } - - iterator & operator++() - { - if (unlikely(ZeroTraits::check(ptr->first))) - ptr = container->buf; - else - ++ptr; - - while (ptr < container->buf + container->buf_size() && ZeroTraits::check(ptr->first)) - ++ptr; - - return *this; - } - - Value & operator* () const { return *ptr; } - Value * operator->() const { return ptr; } - }; - - - class const_iterator - { - const Self * container; - const Value * ptr; - - friend class HashMap; - - const_iterator(const Self * container_, const Value * ptr_) : container(container_), ptr(ptr_) {} - - public: - const_iterator() {} - const_iterator(const iterator & rhs) : container(rhs.container), ptr(rhs.ptr) {} - - bool operator== (const const_iterator & rhs) const { return ptr == rhs.ptr; } - bool operator!= (const const_iterator & rhs) const { return ptr != rhs.ptr; } - - const_iterator & operator++() - { - if (unlikely(ZeroTraits::check(ptr->first))) - ptr = container->buf; - else - ++ptr; - - while (ptr < container->buf + container->buf_size() && ZeroTraits::check(ptr->first)) - ++ptr; - - return *this; - } - - const Value & operator* () const { return *ptr; } - const Value * operator->() const { return ptr; } - }; - - - const_iterator begin() const - { - if (has_zero) - return const_iterator(this, zero_value()); - - const Value * ptr = buf; - while (ptr < buf + buf_size() && ZeroTraits::check(ptr->first)) - ++ptr; - - return const_iterator(this, ptr); - } - - iterator begin() - { - if (has_zero) - return iterator(this, zero_value()); - - Value * ptr = buf; - while (ptr < buf + buf_size() && ZeroTraits::check(ptr->first)) - ++ptr; - - return iterator(this, ptr); - } - - const_iterator end() const { return const_iterator(this, buf + buf_size()); } - iterator end() { return iterator(this, buf + buf_size()); } - - - /// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace. - std::pair insert(const Value & x) - { - if (ZeroTraits::check(x.first)) - { - if (!has_zero) - { - ++m_size; - has_zero = true; - zero_value()->second = x.second; - return std::make_pair(begin(), true); - } - return std::make_pair(begin(), false); - } - - size_t place_value = place(hash(x.first)); - while (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first != x.first) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - iterator res(this, &buf[place_value]); - - if (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first == x.first) - return std::make_pair(res, false); - - buf[place_value] = x; - ++m_size; - - if (unlikely(m_size > max_fill())) - { - resize(); - return std::make_pair(find(x.first), true); - } - - return std::make_pair(res, true); - } - - - /** Вставить ключ, - * вернуть итератор на позицию, которую можно использовать для placement new значения, - * а также флаг - был ли вставлен новый ключ. - * - * Вы обязаны сделать placement new значения, если был вставлен новый ключ, - * так как при уничтожении хэш-таблицы для него будет вызываться деструктор! - * - * Пример использования: - * - * Map::iterator it; - * bool inserted; - * map.emplace(key, it, inserted); - * if (inserted) - * new(&it->second) Value(value); - */ - void emplace(Key x, iterator & it, bool & inserted) - { - if (ZeroTraits::check(x)) - { - if (!has_zero) - { - ++m_size; - has_zero = true; - inserted = true; - } - else - inserted = false; - - it = begin(); - return; - } - - size_t place_value = place(hash(x)); - while (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - it = iterator(this, &buf[place_value]); - - if (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first == x) - { - inserted = false; - return; - } - - new(&buf[place_value].first) Key(x); - inserted = true; - ++m_size; - - if (unlikely(m_size > max_fill())) - { - resize(); - it = find(x); - } - } - - - /// То же самое, но с заранее вычисленным значением хэш-функции. - void emplace(Key x, iterator & it, bool & inserted, size_t hash_value) - { - if (ZeroTraits::check(x)) - { - if (!has_zero) - { - ++m_size; - has_zero = true; - inserted = true; - } - else - inserted = false; - - it = begin(); - return; - } - - size_t place_value = place(hash_value); - while (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - it = iterator(this, &buf[place_value]); - - if (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first == x) - { - inserted = false; - return; - } - - new(&buf[place_value].first) Key(x); - inserted = true; - ++m_size; - - if (unlikely(m_size > max_fill())) - { - resize(); - it = find(x); - } - } - - - iterator find(Key x) - { - if (ZeroTraits::check(x)) - return has_zero ? begin() : end(); - - size_t place_value = place(hash(x)); - while (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - return !ZeroTraits::check(buf[place_value].first) ? iterator(this, &buf[place_value]) : end(); - } - - - const_iterator find(Key x) const - { - if (ZeroTraits::check(x)) - return has_zero ? begin() : end(); - - size_t place_value = place(hash(x.first)); - while (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - return !ZeroTraits::check(buf[place_value].first) ? const_iterator(this, &buf[place_value]) : end(); - } - - - size_t size() const - { - return m_size; - } - - bool empty() const - { - return 0 == m_size; - } - - Mapped & operator[](Key x) - { - if (ZeroTraits::check(x)) - { - if (!has_zero) - { - ++m_size; - has_zero = true; - } - return zero_value()->second; - } - - size_t place_value = place(hash(x)); - while (!ZeroTraits::check(buf[place_value].first) && buf[place_value].first != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - if (!ZeroTraits::check(buf[place_value].first)) - return buf[place_value].second; - - new(&buf[place_value].first) Key(x); - new(&buf[place_value].second) Mapped(); - ++m_size; - - if (unlikely(m_size > max_fill())) - { - resize(); - return (*this)[x]; - } - - return buf[place_value].second; - } - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - size_t getCollisions() const - { - return collisions; - } -#endif -}; - - -} diff --git a/dbms/include/DB/Interpreters/HashSet.h b/dbms/include/DB/Interpreters/HashSet.h deleted file mode 100644 index 7856eff6072..00000000000 --- a/dbms/include/DB/Interpreters/HashSet.h +++ /dev/null @@ -1,510 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - - -namespace DB -{ - - -/** См. HashMap.h - */ - -template -< - typename Key, - typename Hash = default_hash, - typename ZeroTraits = default_zero_traits, - typename GrowthTraits = default_growth_traits, - typename Allocator = HashTableAllocator -> -class HashSet : private boost::noncopyable, private Hash, private Allocator /// empty base optimization -{ -private: - friend class const_iterator; - friend class iterator; - - typedef size_t HashValue; - typedef HashSet Self; - - size_t m_size; /// Количество элементов - Key * buf; /// Кусок памяти для всех элементов кроме элемента с ключём 0. - UInt8 size_degree; /// Размер таблицы в виде степени двух - bool has_zero; /// Хэш-таблица содержит элемент со значением ключа = 0. - - static Key zero_value; /// Нулевое значение ключа. Чтобы было, куда поставить итератор. - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - mutable size_t collisions; -#endif - - inline size_t hash(const Key & x) const { return Hash::operator()(x); } - inline size_t buf_size() const { return 1 << size_degree; } - inline size_t buf_size_bytes() const { return buf_size() * sizeof(Key); } - inline size_t max_fill() const { return 1 << (size_degree - 1); } - inline size_t mask() const { return buf_size() - 1; } - inline size_t place(HashValue x) const { return x & mask(); } - - - void alloc() - { - buf = reinterpret_cast(Allocator::alloc(buf_size_bytes())); - } - - void free() - { - Allocator::free(buf, buf_size_bytes()); - } - - - /// Увеличить размер буфера в 2 ^ N раз или до new_size_degree, если передан ненулевой аргумент. - void resize(size_t new_size_degree = 0) - { -#ifdef DBMS_HASH_MAP_DEBUG_RESIZES - Stopwatch watch; -#endif - - size_t old_size = buf_size(); - size_t old_size_bytes = buf_size_bytes(); - - if (new_size_degree) - size_degree = new_size_degree; - else - size_degree += size_degree >= GrowthTraits::GROWTH_CHANGE_THRESHOLD - ? 1 - : GrowthTraits::FAST_GROWTH_DEGREE; - - /// Расширим пространство. - buf = reinterpret_cast(Allocator::realloc(buf, old_size_bytes, buf_size_bytes())); - - /** Теперь некоторые элементы может потребоваться переместить на новое место. - * Элемент может остаться на месте, или переместиться в новое место "справа", - * или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа". - */ - size_t i = 0; - for (; i < old_size; ++i) - if (!ZeroTraits::check(buf[i])) - reinsert(buf[i]); - - /** Также имеется особый случай: - * если элемент должен был быть в конце старого буфера, [ x] - * но находится в начале из-за цепочки разрешения коллизий, [o x] - * то после ресайза, он сначала снова окажется не на своём месте, [ xo ] - * и для того, чтобы перенести его куда надо, - * надо будет после переноса всех элементов из старой половинки [ o x ] - * обработать ещё хвостик из цепочки разрешения коллизий сразу после неё [ o x ] - */ - for (; !ZeroTraits::check(buf[i]); ++i) - reinsert(buf[i]); - -#ifdef DBMS_HASH_MAP_DEBUG_RESIZES - watch.stop(); - std::cerr << std::fixed << std::setprecision(3) - << "Resize from " << old_size << " to " << buf_size() << " took " << watch.elapsedSeconds() << " sec." - << std::endl; -#endif - } - - - /** Вставить в новый буфер значение, которое было в старом буфере. - * Используется при увеличении размера буфера. - */ - void reinsert(Key & x) - { - size_t place_value = place(hash(x)); - - /// Если элемент на своём месте. - if (&x == &buf[place_value]) - return; - - /// Вычисление нового места, с учётом цепочки разрешения коллизий. - while (!ZeroTraits::check(buf[place_value]) && x != buf[place_value]) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - /// Если элемент остался на своём месте в старой цепочке разрешения коллизий. - if (!ZeroTraits::check(buf[place_value]) && x == buf[place_value]) - return; - - /// Копирование на новое место и зануление старого. - memcpy(&buf[place_value], &x, sizeof(x)); - ZeroTraits::set(x); - - /// Потом на старое место могут переместиться элементы, которые раньше были в коллизии с этим. - } - - -public: - typedef Key key_type; - typedef Key value_type; - - - HashSet() : - m_size(0), - size_degree(GrowthTraits::INITIAL_SIZE_DEGREE), - has_zero(false) - { - ZeroTraits::set(zero_value); - alloc(); - } - - ~HashSet() - { - if (!__has_trivial_destructor(Key)) - for (iterator it = begin(); it != end(); ++it) - it->~Key(); - - free(); - } - - - class iterator - { - Self * container; - Key * ptr; - - friend class HashSet; - - iterator(Self * container_, Key * ptr_) : container(container_), ptr(ptr_) {} - - public: - iterator() {} - - bool operator== (const iterator & rhs) const { return ptr == rhs.ptr; } - bool operator!= (const iterator & rhs) const { return ptr != rhs.ptr; } - - iterator & operator++() - { - if (unlikely(ptr == &container->zero_value)) - ptr = container->buf; - else - ++ptr; - - while (ptr < container->buf + container->buf_size() && ZeroTraits::check(*ptr)) - ++ptr; - - return *this; - } - - Key & operator* () const { return *ptr; } - Key * operator->() const { return ptr; } - }; - - - class const_iterator - { - const Self * container; - const Key * ptr; - - friend class HashSet; - - const_iterator(const Self * container_, const Key * ptr_) : container(container_), ptr(ptr_) {} - - public: - const_iterator() {} - const_iterator(const iterator & rhs) : container(rhs.container), ptr(rhs.ptr) {} - - bool operator== (const const_iterator & rhs) const { return ptr == rhs.ptr; } - bool operator!= (const const_iterator & rhs) const { return ptr != rhs.ptr; } - - const_iterator & operator++() - { - if (unlikely(ptr == &container->zero_value)) - ptr = container->buf; - else - ++ptr; - - while (ptr < container->buf + container->buf_size() && ZeroTraits::check(*ptr)) - ++ptr; - - return *this; - } - - const Key & operator* () const { return *ptr; } - const Key * operator->() const { return ptr; } - }; - - - const_iterator begin() const - { - if (has_zero) - return const_iterator(this, &zero_value); - - const Key * ptr = buf; - while (ptr < buf + buf_size() && ZeroTraits::check(*ptr)) - ++ptr; - - return const_iterator(this, ptr); - } - - iterator begin() - { - if (has_zero) - return iterator(this, &zero_value); - - Key * ptr = buf; - while (ptr < buf + buf_size() && ZeroTraits::check(*ptr)) - ++ptr; - - return iterator(this, ptr); - } - - const_iterator end() const { return const_iterator(this, buf + buf_size()); } - iterator end() { return iterator(this, buf + buf_size()); } - - - /// Вставить ключ. - std::pair insert(const Key & x) - { - if (ZeroTraits::check(x)) - { - if (!has_zero) - { - ++m_size; - has_zero = true; - return std::make_pair(begin(), true); - } - return std::make_pair(begin(), false); - } - - size_t place_value = place(hash(x)); - while (!ZeroTraits::check(buf[place_value]) && buf[place_value] != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - iterator res(this, &buf[place_value]); - - if (!ZeroTraits::check(buf[place_value]) && buf[place_value] == x) - return std::make_pair(res, false); - - buf[place_value] = x; - ++m_size; - - if (unlikely(m_size > max_fill())) - { - resize(); - return std::make_pair(find(x), true); - } - - return std::make_pair(res, true); - } - - - void emplace(Key x, iterator & it, bool & inserted) - { - if (ZeroTraits::check(x)) - { - if (!has_zero) - { - ++m_size; - has_zero = true; - inserted = true; - } - else - inserted = false; - - it = begin(); - return; - } - - size_t place_value = place(hash(x)); - while (!ZeroTraits::check(buf[place_value]) && buf[place_value] != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - it = iterator(this, &buf[place_value]); - - if (!ZeroTraits::check(buf[place_value]) && buf[place_value] == x) - { - inserted = false; - return; - } - - new(&buf[place_value]) Key(x); - inserted = true; - ++m_size; - - if (unlikely(m_size > max_fill())) - { - resize(); - it = find(x); - } - } - - - void merge(const Self & rhs) - { - if (!has_zero && rhs.has_zero) - { - has_zero = true; - ++m_size; - } - - for (size_t i = 0; i < rhs.buf_size(); ++i) - if (!ZeroTraits::check(rhs.buf[i])) - insert(rhs.buf[i]); - } - - - iterator find(Key x) - { - if (ZeroTraits::check(x)) - return has_zero ? begin() : end(); - - size_t place_value = place(hash(x)); - while (!ZeroTraits::check(buf[place_value]) && buf[place_value] != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - return !ZeroTraits::check(buf[place_value]) ? iterator(this, &buf[place_value]) : end(); - } - - - const_iterator find(Key x) const - { - if (ZeroTraits::check(x)) - return has_zero ? begin() : end(); - - size_t place_value = place(hash(x)); - while (!ZeroTraits::check(buf[place_value]) && buf[place_value] != x) - { - ++place_value; - place_value &= mask(); -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - ++collisions; -#endif - } - - return !ZeroTraits::check(buf[place_value]) ? const_iterator(this, &buf[place_value]) : end(); - } - - - void write(DB::WriteBuffer & wb) const - { - DB::writeVarUInt(m_size, wb); - - if (has_zero) - DB::writeBinary(zero_value, wb); - - for (size_t i = 0; i < buf_size(); ++i) - if (!ZeroTraits::check(buf[i])) - DB::writeBinary(buf[i], wb); - } - - void read(DB::ReadBuffer & rb) - { - has_zero = false; - m_size = 0; - - size_t new_size = 0; - DB::readVarUInt(new_size, rb); - - free(); - - size_degree = new_size <= 1 - ? GrowthTraits::INITIAL_SIZE_DEGREE - : ((GrowthTraits::INITIAL_SIZE_DEGREE > static_cast(log2(new_size - 1)) + 2) - ? GrowthTraits::INITIAL_SIZE_DEGREE - : (static_cast(log2(new_size - 1)) + 2)); - - alloc(); - - for (size_t i = 0; i < new_size; ++i) - { - Key x; - DB::readBinary(x, rb); - insert(x); - } - } - - void readAndMerge(DB::ReadBuffer & rb) - { - size_t new_size = 0; - DB::readVarUInt(new_size, rb); - - size_t new_size_degree = new_size <= 1 - ? GrowthTraits::INITIAL_SIZE_DEGREE - : ((GrowthTraits::INITIAL_SIZE_DEGREE > static_cast(log2(new_size - 1)) + 2) - ? GrowthTraits::INITIAL_SIZE_DEGREE - : (static_cast(log2(new_size - 1)) + 2)); - - if (new_size_degree > size_degree) - resize(new_size_degree); - - for (size_t i = 0; i < new_size; ++i) - { - Key x; - DB::readBinary(x, rb); - insert(x); - } - } - - void writeText(DB::WriteBuffer & wb) const - { - /// Используется в шаблонном коде. - throw Exception("Method HashSet::writeText is not implemented", ErrorCodes::NOT_IMPLEMENTED); - } - - - size_t size() const - { - return m_size; - } - - bool empty() const - { - return 0 == m_size; - } - - size_t getBufferSizeInBytes() const - { - return buf_size_bytes(); - } - -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - size_t getCollisions() const - { - return collisions; - } -#endif -}; - - -template -< - typename Key, - typename Hash, - typename ZeroTraits, - typename GrowthTraits, - typename Allocator -> -Key HashSet::zero_value{}; - - -}