mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-21 09:10:48 +00:00
Merge
This commit is contained in:
commit
0933f94c5d
@ -8,7 +8,7 @@
|
||||
|
||||
#include <DB/Columns/ColumnArray.h>
|
||||
|
||||
#include <DB/Interpreters/HashSet.h>
|
||||
#include <DB/Common/HashTable/HashSet.h>
|
||||
|
||||
#include <DB/AggregateFunctions/AggregateFunctionGroupArray.h>
|
||||
|
||||
@ -22,18 +22,18 @@ namespace DB
|
||||
template <typename T>
|
||||
struct AggregateFunctionGroupUniqArrayData
|
||||
{
|
||||
struct GrowthTraits : public default_growth_traits
|
||||
/// При создании, хэш-таблица должна быть небольшой.
|
||||
struct Grower : public HashTableGrower
|
||||
{
|
||||
/// При создании, хэш-таблица должна быть небольшой.
|
||||
static const int INITIAL_SIZE_DEGREE = 4;
|
||||
static const size_t initial_size_degree = 16;
|
||||
Grower() { size_degree = initial_size_degree; }
|
||||
};
|
||||
|
||||
typedef HashSet<
|
||||
T,
|
||||
default_hash<T>,
|
||||
default_zero_traits<T>,
|
||||
GrowthTraits,
|
||||
HashTableAllocatorWithStackMemory<sizeof(T) * (1 << GrowthTraits::INITIAL_SIZE_DEGREE)>
|
||||
DefaultHash<T>,
|
||||
Grower,
|
||||
HashTableAllocatorWithStackMemory<sizeof(T) * (1 << Grower::initial_size_degree)>
|
||||
> Set;
|
||||
|
||||
Set value;
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include <DB/DataTypes/DataTypeString.h>
|
||||
|
||||
#include <DB/Interpreters/AggregationCommon.h>
|
||||
#include <DB/Interpreters/HashSet.h>
|
||||
#include <DB/Common/HashTable/HashSet.h>
|
||||
|
||||
#include <DB/Columns/ColumnString.h>
|
||||
|
||||
@ -74,17 +74,17 @@ struct AggregateFunctionUniqExactData
|
||||
{
|
||||
typedef T Key;
|
||||
|
||||
struct GrowthTraits : public default_growth_traits
|
||||
/// При создании, хэш-таблица должна быть небольшой.
|
||||
struct Grower : public HashTableGrower
|
||||
{
|
||||
/// При создании, хэш-таблица должна быть небольшой.
|
||||
static const int INITIAL_SIZE_DEGREE = 64 / sizeof(Key);
|
||||
static const size_t initial_size_degree = 64 / sizeof(Key);
|
||||
Grower() { size_degree = initial_size_degree; }
|
||||
};
|
||||
|
||||
typedef HashSet<
|
||||
Key,
|
||||
default_hash<Key>,
|
||||
default_zero_traits<Key>,
|
||||
GrowthTraits,
|
||||
DefaultHash<Key>,
|
||||
Grower,
|
||||
HashTableAllocatorWithStackMemory<64>
|
||||
> Set;
|
||||
|
||||
@ -99,17 +99,17 @@ struct AggregateFunctionUniqExactData<String>
|
||||
{
|
||||
typedef UInt128 Key;
|
||||
|
||||
struct GrowthTraits : public default_growth_traits
|
||||
/// При создании, хэш-таблица должна быть небольшой.
|
||||
struct Grower : public HashTableGrower
|
||||
{
|
||||
/// При создании, хэш-таблица должна быть небольшой.
|
||||
static const int INITIAL_SIZE_DEGREE = 64 / sizeof(Key);
|
||||
static const size_t initial_size_degree = 64 / sizeof(Key);
|
||||
Grower() { size_degree = initial_size_degree; }
|
||||
};
|
||||
|
||||
typedef HashSet<
|
||||
Key,
|
||||
UInt128TrivialHash,
|
||||
UInt128ZeroTraits,
|
||||
GrowthTraits,
|
||||
Grower,
|
||||
HashTableAllocatorWithStackMemory<64>
|
||||
> Set;
|
||||
|
||||
|
@ -79,7 +79,7 @@ public:
|
||||
|
||||
StringRef getDataAt(size_t n) const
|
||||
{
|
||||
/** Работает для массивов чисел.
|
||||
/** Работает для массивов значений фиксированной длины.
|
||||
* Для массивов строк и массивов массивов полученный кусок памяти может не взаимно-однозначно соответствовать элементам.
|
||||
*/
|
||||
StringRef begin = data->getDataAt(offsetAt(n));
|
||||
@ -89,7 +89,20 @@ public:
|
||||
|
||||
void insertData(const char * pos, size_t length)
|
||||
{
|
||||
throw Exception("Method insertData is not supported for " + getName(), ErrorCodes::NOT_IMPLEMENTED);
|
||||
/** Аналогично - только для массивов значений фиксированной длины.
|
||||
*/
|
||||
IColumn * data_ = data.get();
|
||||
if (!data_->isFixed())
|
||||
throw Exception("Method insertData is not supported for " + getName(), ErrorCodes::NOT_IMPLEMENTED);
|
||||
|
||||
size_t field_size = data_->sizeOfField();
|
||||
|
||||
const char * end = pos + length;
|
||||
for (; pos + field_size <= end; pos += field_size)
|
||||
data_->insertData(pos, field_size);
|
||||
|
||||
if (pos != end)
|
||||
throw Exception("Incorrect length argument for method ColumnArray::insertData", ErrorCodes::BAD_ARGUMENTS);
|
||||
}
|
||||
|
||||
ColumnPtr cut(size_t start, size_t length) const
|
||||
|
@ -26,7 +26,7 @@ namespace DB
|
||||
* Предназначен для ситуаций, в которых создаётся много массивов одинакового небольшого размера,
|
||||
* но при этом размер не известен во время компиляции.
|
||||
* Также даёт существенное преимущество в случаях, когда важно, чтобы sizeof был минимальным.
|
||||
* Например, если массивы кладутся в open-addressing хэш-таблицу с inplace хранением значений (как DB::HashMap)
|
||||
* Например, если массивы кладутся в open-addressing хэш-таблицу с inplace хранением значений (как HashMap)
|
||||
*
|
||||
* В этом случае, по сравнению с std::vector:
|
||||
* - для массивов размером в 1 элемент - преимущество примерно в 2 раза;
|
||||
|
68
dbms/include/DB/Common/HashTable/ClearableHashMap.h
Normal file
68
dbms/include/DB/Common/HashTable/ClearableHashMap.h
Normal file
@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
|
||||
|
||||
/** Хеш-таблица, позволяющая очищать таблицу за O(1).
|
||||
* Еще более простая, чем HashMap: Key и Mapped должны быть POD-типами.
|
||||
*
|
||||
* Вместо этого класса можно было бы просто использовать в HashMap в качестве ключа пару <версия, ключ>,
|
||||
* но тогда таблица накапливала бы все ключи, которые в нее когда-либо складывали, и неоправданно росла.
|
||||
* Этот класс идет на шаг дальше и считает ключи со старой версией пустыми местами в хеш-таблице.
|
||||
*/
|
||||
|
||||
struct ClearableHashMapState
|
||||
{
|
||||
UInt32 version = 1;
|
||||
|
||||
/// Сериализация, в бинарном и текстовом виде.
|
||||
void write(DB::WriteBuffer & wb) const { DB::writeBinary(version, wb); }
|
||||
void writeText(DB::WriteBuffer & wb) const { DB::writeText(version, wb); }
|
||||
|
||||
/// Десериализация, в бинарном и текстовом виде.
|
||||
void read(DB::ReadBuffer & rb) { DB::readBinary(version, rb); }
|
||||
void readText(DB::ReadBuffer & rb) { DB::readText(version, rb); }
|
||||
};
|
||||
|
||||
|
||||
template <typename Key, typename Mapped, typename Hash>
|
||||
struct ClearableHashMapCell : public HashMapCell<Key, Mapped, Hash, ClearableHashMapState>
|
||||
{
|
||||
typedef ClearableHashMapState State;
|
||||
typedef HashMapCell<Key, Mapped, Hash, ClearableHashMapState> Base;
|
||||
typedef typename Base::value_type value_type;
|
||||
|
||||
UInt32 version;
|
||||
|
||||
ClearableHashMapCell() {}
|
||||
ClearableHashMapCell(const Key & key_, const State & state) : Base(key_, state), version(state.version) {}
|
||||
ClearableHashMapCell(const value_type & value_, const State & state) : Base(value_, state), version(state.version) {}
|
||||
|
||||
bool isZero(const State & state) const { return version != state.version; }
|
||||
static bool isZero(const Key & key, const State & state) { return false; }
|
||||
|
||||
/// Установить значение ключа в ноль.
|
||||
void setZero() { version = 0; }
|
||||
|
||||
/// Нужно ли хранить нулевой ключ отдельно (то есть, могут ли в хэш-таблицу вставить нулевой ключ).
|
||||
static constexpr bool need_zero_value_storage = false;
|
||||
};
|
||||
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Mapped,
|
||||
typename Hash = DefaultHash<Key>,
|
||||
typename Grower = HashTableGrower,
|
||||
typename Allocator = HashTableAllocator
|
||||
>
|
||||
class ClearableHashMap : public HashMapTable<Key, ClearableHashMapCell<Key, Mapped, Hash>, Hash, Grower, Allocator>
|
||||
{
|
||||
public:
|
||||
void clear()
|
||||
{
|
||||
++this->version;
|
||||
this->m_size = 0;
|
||||
}
|
||||
};
|
45
dbms/include/DB/Common/HashTable/Hash.h
Normal file
45
dbms/include/DB/Common/HashTable/Hash.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <stats/IntHash.h>
|
||||
#include <DB/Core/Types.h>
|
||||
|
||||
|
||||
/** Хэш функции, которые лучше чем тривиальная функция std::hash.
|
||||
* (при агрегации по идентификатору посетителя, прирост производительности более чем в 5 раз)
|
||||
*/
|
||||
template <typename T> struct DefaultHash;
|
||||
|
||||
template <typename T>
|
||||
inline size_t DefaultHash64(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 DefaultHash<T>\
|
||||
{\
|
||||
size_t operator() (T key) const\
|
||||
{\
|
||||
return DefaultHash64<T>(key);\
|
||||
}\
|
||||
};
|
||||
|
||||
DEFAULT_HASH_64(DB::UInt8)
|
||||
DEFAULT_HASH_64(DB::UInt16)
|
||||
DEFAULT_HASH_64(DB::UInt32)
|
||||
DEFAULT_HASH_64(DB::UInt64)
|
||||
DEFAULT_HASH_64(DB::Int8)
|
||||
DEFAULT_HASH_64(DB::Int16)
|
||||
DEFAULT_HASH_64(DB::Int32)
|
||||
DEFAULT_HASH_64(DB::Int64)
|
||||
DEFAULT_HASH_64(DB::Float32)
|
||||
DEFAULT_HASH_64(DB::Float64)
|
||||
|
||||
#undef DEFAULT_HASH_64
|
114
dbms/include/DB/Common/HashTable/HashMap.h
Normal file
114
dbms/include/DB/Common/HashTable/HashMap.h
Normal file
@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Common/HashTable/Hash.h>
|
||||
#include <DB/Common/HashTable/HashTable.h>
|
||||
#include <DB/Common/HashTable/HashTableAllocator.h>
|
||||
|
||||
|
||||
template <typename Key, typename TMapped, typename Hash, typename TState = HashTableNoState>
|
||||
struct HashMapCell
|
||||
{
|
||||
typedef TMapped Mapped;
|
||||
typedef TState State;
|
||||
|
||||
typedef std::pair<Key, Mapped> value_type;
|
||||
value_type value;
|
||||
|
||||
HashMapCell() {}
|
||||
HashMapCell(const Key & key_, const State & state) : value(key_, Mapped()) {}
|
||||
HashMapCell(const value_type & value_, const State & state) : value(value_) {}
|
||||
|
||||
value_type & getValue() { return value; }
|
||||
const value_type & getValue() const { return value; }
|
||||
|
||||
static Key & getKey(value_type & value) { return value.first; }
|
||||
static const Key & getKey(const value_type & value) { return value.first; }
|
||||
|
||||
bool keyEquals(const Key & key_) const { return value.first == key_; }
|
||||
bool keyEquals(const HashMapCell & other) const { return value.first == other.value.first; }
|
||||
|
||||
void setHash(size_t hash_value) {}
|
||||
size_t getHash(const Hash & hash) const { return hash(value.first); }
|
||||
|
||||
bool isZero(const State & state) const { return isZero(value.first, state); }
|
||||
static bool isZero(const Key & key, const State & state) { return ZeroTraits::check(key); }
|
||||
|
||||
/// Установить значение ключа в ноль.
|
||||
void setZero() { ZeroTraits::set(value.first); }
|
||||
|
||||
/// Нужно ли хранить нулевой ключ отдельно (то есть, могут ли в хэш-таблицу вставить нулевой ключ).
|
||||
static constexpr bool need_zero_value_storage = true;
|
||||
|
||||
/// Является ли ячейка удалённой.
|
||||
bool isDeleted() const { return false; }
|
||||
|
||||
void setMapped(const value_type & value_) { value.second = value_.second; }
|
||||
|
||||
/// Сериализация, в бинарном и текстовом виде.
|
||||
void write(DB::WriteBuffer & wb) const
|
||||
{
|
||||
DB::writeBinary(value.first, wb);
|
||||
DB::writeBinary(value.second, wb);
|
||||
}
|
||||
|
||||
void writeText(DB::WriteBuffer & wb) const
|
||||
{
|
||||
DB::writeDoubleQuoted(value.first, wb);
|
||||
DB::writeChar(',', wb);
|
||||
DB::writeDoubleQuoted(value.second, wb);
|
||||
}
|
||||
|
||||
/// Десериализация, в бинарном и текстовом виде.
|
||||
void read(DB::ReadBuffer & rb)
|
||||
{
|
||||
DB::readBinary(value.first, rb);
|
||||
DB::readBinary(value.second, rb);
|
||||
}
|
||||
|
||||
void readText(DB::ReadBuffer & rb)
|
||||
{
|
||||
DB::readDoubleQuoted(value.first, rb);
|
||||
DB::assertString(",", rb);
|
||||
DB::readDoubleQuoted(value.second, rb);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Cell,
|
||||
typename Hash = DefaultHash<Key>,
|
||||
typename Grower = HashTableGrower,
|
||||
typename Allocator = HashTableAllocator
|
||||
>
|
||||
class HashMapTable : public HashTable<Key, Cell, Hash, Grower, Allocator>
|
||||
{
|
||||
public:
|
||||
typedef Key key_type;
|
||||
typedef typename Cell::Mapped mapped_type;
|
||||
typedef typename Cell::value_type value_type;
|
||||
|
||||
mapped_type & operator[](Key x)
|
||||
{
|
||||
typename HashMapTable::iterator it;
|
||||
bool inserted;
|
||||
this->emplace(x, it, inserted);
|
||||
|
||||
if (inserted)
|
||||
new(&it->second) mapped_type();
|
||||
|
||||
return it->second;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Mapped,
|
||||
typename Hash = DefaultHash<Key>,
|
||||
typename Grower = HashTableGrower,
|
||||
typename Allocator = HashTableAllocator
|
||||
>
|
||||
using HashMap = HashMapTable<Key, HashMapCell<Key, Mapped, Hash>, Hash, Grower, Allocator>;
|
57
dbms/include/DB/Common/HashTable/HashSet.h
Normal file
57
dbms/include/DB/Common/HashTable/HashSet.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Common/HashTable/Hash.h>
|
||||
#include <DB/Common/HashTable/HashTable.h>
|
||||
#include <DB/Common/HashTable/HashTableAllocator.h>
|
||||
|
||||
#include <DB/IO/WriteBuffer.h>
|
||||
#include <DB/IO/WriteHelpers.h>
|
||||
#include <DB/IO/ReadBuffer.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
#include <DB/IO/VarInt.h>
|
||||
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Hash = DefaultHash<Key>,
|
||||
typename Grower = HashTableGrower,
|
||||
typename Allocator = HashTableAllocator
|
||||
>
|
||||
class HashSet : public HashTable<Key, HashTableCell<Key, Hash>, Hash, Grower, Allocator>
|
||||
{
|
||||
public:
|
||||
typedef HashSet<Key, Hash, Grower, Allocator> Self;
|
||||
typedef HashTableCell<Key, Hash> Cell;
|
||||
|
||||
void merge(const Self & rhs)
|
||||
{
|
||||
if (!this->hasZero() && rhs.hasZero())
|
||||
{
|
||||
this->setHasZero();
|
||||
++this->m_size;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < rhs.grower.bufSize(); ++i)
|
||||
if (!rhs.buf[i].isZero(*this))
|
||||
this->insert(Cell::getKey(rhs.buf[i].getValue()));
|
||||
}
|
||||
|
||||
|
||||
void readAndMerge(DB::ReadBuffer & rb)
|
||||
{
|
||||
Cell::State::read(rb);
|
||||
|
||||
size_t new_size = 0;
|
||||
DB::readVarUInt(new_size, rb);
|
||||
|
||||
this->resize(new_size);
|
||||
|
||||
for (size_t i = 0; i < new_size; ++i)
|
||||
{
|
||||
Cell x;
|
||||
x.read(rb);
|
||||
this->insert(Cell::getKey(x.getValue()));
|
||||
}
|
||||
}
|
||||
};
|
689
dbms/include/DB/Common/HashTable/HashTable.h
Normal file
689
dbms/include/DB/Common/HashTable/HashTable.h
Normal file
@ -0,0 +1,689 @@
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <malloc.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <Yandex/likely.h>
|
||||
|
||||
#include <stats/IntHash.h>
|
||||
|
||||
#include <DB/Core/Types.h>
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
|
||||
#include <DB/IO/WriteBuffer.h>
|
||||
#include <DB/IO/WriteHelpers.h>
|
||||
#include <DB/IO/ReadBuffer.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
#include <DB/IO/VarInt.h>
|
||||
|
||||
#include <DB/Common/HashTable/HashTableAllocator.h>
|
||||
|
||||
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <statdaemons/Stopwatch.h>
|
||||
#endif
|
||||
|
||||
|
||||
/** Состояние хэш-таблицы, которое влияет на свойства её ячеек.
|
||||
* Используется в качестве параметра шаблона.
|
||||
* Например, существует реализация мгновенно-очищаемой хэш-таблицы - ClearableHashMap.
|
||||
* Для неё, в каждой ячейке хранится номер версии, и в самой хэш-таблице - текущая версия.
|
||||
* При очистке, просто увеличивается текущая версия; все ячейки с несовпадающей версией считаются пустыми.
|
||||
* Другой пример: для приближённого рассчёта количества уникальных посетителей, есть хэш-таблица UniquesHashSet.
|
||||
* В ней имеется понятие "степень". При каждом переполнении, ячейки с ключами, не делящимися на соответствующую степень двух, удаляются.
|
||||
*/
|
||||
struct HashTableNoState
|
||||
{
|
||||
/// Сериализация, в бинарном и текстовом виде.
|
||||
void write(DB::WriteBuffer & wb) const {}
|
||||
void writeText(DB::WriteBuffer & wb) const {}
|
||||
|
||||
/// Десериализация, в бинарном и текстовом виде.
|
||||
void read(DB::ReadBuffer & rb) {}
|
||||
void readText(DB::ReadBuffer & rb) {}
|
||||
};
|
||||
|
||||
|
||||
/// Эти функции могут быть перегружены для пользовательских типов.
|
||||
namespace ZeroTraits
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
bool check(const T x) { return x == 0; }
|
||||
|
||||
template <typename T>
|
||||
void set(T & x) { x = 0; }
|
||||
|
||||
};
|
||||
|
||||
|
||||
/** Compile-time интерфейс ячейки хэш-таблицы.
|
||||
* Разные ячейки используются для реализации разных хэш-таблиц.
|
||||
* Ячейка должна содержать ключ.
|
||||
* Также может содержать значение и произвольные дополнительные данные
|
||||
* (пример: запомненное значение хэш-функции; номер версии для ClearableHashMap).
|
||||
*/
|
||||
template <typename Key, typename Hash, typename TState = HashTableNoState>
|
||||
struct HashTableCell
|
||||
{
|
||||
typedef TState State;
|
||||
|
||||
typedef Key value_type;
|
||||
Key key;
|
||||
|
||||
HashTableCell() {}
|
||||
|
||||
/// Создать ячейку с заданным ключём / ключём и значением.
|
||||
HashTableCell(const Key & key_, const State & state) : key(key_) {}
|
||||
/// HashTableCell(const value_type & value_, const State & state) : key(value_) {}
|
||||
|
||||
/// Получить то, что будет value_type контейнера.
|
||||
value_type & getValue() { return key; }
|
||||
const value_type & getValue() const { return key; }
|
||||
|
||||
/// Получить ключ.
|
||||
static Key & getKey(value_type & value) { return value; }
|
||||
static const Key & getKey(const value_type & value) { return value; }
|
||||
|
||||
/// Равны ли ключи у ячеек.
|
||||
bool keyEquals(const Key & key_) const { return key == key_; }
|
||||
bool keyEquals(const HashTableCell & other) const { return key == other.key; }
|
||||
|
||||
/// Если ячейка умеет запоминать в себе значение хэш-функции, то запомнить его.
|
||||
void setHash(size_t hash_value) {}
|
||||
|
||||
/// Если ячейка умеет запоминать в себе значение хэш-функции, то вернуть запомненное значение.
|
||||
/// Оно должно быть хотя бы один раз вычислено до этого.
|
||||
/// Если запоминание значения хэш-функции не предусмотрено, то просто вычислить хэш.
|
||||
size_t getHash(const Hash & hash) const { return hash(key); }
|
||||
|
||||
/// Является ли ключ нулевым. В основном буфере, ячейки с нулевым ключём, считаются пустыми.
|
||||
/// Если нулевые ключи могут быть вставлены в таблицу, то ячейка для нулевого ключа хранится отдельно, не в основном буфере.
|
||||
/// Нулевые ключи должны быть такими, что занулённый кусок памяти представляет собой нулевой ключ.
|
||||
bool isZero(const State & state) const { return isZero(key, state); }
|
||||
static bool isZero(const Key & key, const State & state) { return ZeroTraits::check(key); }
|
||||
|
||||
/// Установить значение ключа в ноль.
|
||||
void setZero() { ZeroTraits::set(key); }
|
||||
|
||||
/// Нужно ли хранить нулевой ключ отдельно (то есть, могут ли в хэш-таблицу вставить нулевой ключ).
|
||||
static constexpr bool need_zero_value_storage = true;
|
||||
|
||||
/// Является ли ячейка удалённой.
|
||||
bool isDeleted() const { return false; }
|
||||
|
||||
/// Установить отображаемое значение, если есть (для HashMap), в соответствующиее из value.
|
||||
void setMapped(const value_type & value) {}
|
||||
|
||||
/// Сериализация, в бинарном и текстовом виде.
|
||||
void write(DB::WriteBuffer & wb) const { DB::writeBinary(key, wb); }
|
||||
void writeText(DB::WriteBuffer & wb) const { DB::writeDoubleQuoted(key, wb); }
|
||||
|
||||
/// Десериализация, в бинарном и текстовом виде.
|
||||
void read(DB::ReadBuffer & rb) { DB::readBinary(key, rb); }
|
||||
void readText(DB::ReadBuffer & rb) { DB::writeDoubleQuoted(key, rb); }
|
||||
};
|
||||
|
||||
|
||||
/** Определяет размер хэш-таблицы, а также когда и во сколько раз её надо ресайзить.
|
||||
*/
|
||||
struct HashTableGrower
|
||||
{
|
||||
/// Состояние этой структуры достаточно, чтобы получить размер буфера хэш-таблицы.
|
||||
|
||||
/// Определяет начальный размер хэш-таблицы.
|
||||
static const size_t initial_size_degree = 16;
|
||||
|
||||
UInt8 size_degree = initial_size_degree;
|
||||
|
||||
/// Размер хэш-таблицы в ячейках.
|
||||
size_t bufSize() const { return 1 << size_degree; }
|
||||
|
||||
size_t maxFill() const { return 1 << (size_degree - 1); }
|
||||
size_t mask() const { return bufSize() - 1; }
|
||||
|
||||
/// Из значения хэш-функции получить номер ячейки в хэш-таблице.
|
||||
size_t place(size_t x) const { return x & mask(); }
|
||||
|
||||
/// Следующая ячейка в цепочке разрешения коллизий.
|
||||
size_t next(size_t pos) const { ++pos; return pos & mask(); }
|
||||
|
||||
/// Является ли хэш-таблица достаточно заполненной. Нужно увеличить размер хэш-таблицы, или удалить из неё что-нибудь ненужное.
|
||||
bool overflow(size_t elems) const { return elems > maxFill(); }
|
||||
|
||||
/// Увеличить размер хэш-таблицы.
|
||||
void increaseSize()
|
||||
{
|
||||
size_degree += size_degree >= 23 ? 1 : 2;
|
||||
}
|
||||
|
||||
/// Установить размер буфера по количеству элементов хэш-таблицы. Используется при десериализации хэш-таблицы.
|
||||
void set(size_t num_elems)
|
||||
{
|
||||
size_degree = num_elems <= 1
|
||||
? initial_size_degree
|
||||
: ((initial_size_degree > static_cast<size_t>(log2(num_elems - 1)) + 2)
|
||||
? initial_size_degree
|
||||
: (static_cast<size_t>(log2(num_elems - 1)) + 2));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Если нужно хранить нулевой ключ отдельно - место для его хранения. */
|
||||
template <bool need_zero_value_storage, typename Cell>
|
||||
struct ZeroValueStorage;
|
||||
|
||||
template <typename Cell>
|
||||
struct ZeroValueStorage<true, Cell>
|
||||
{
|
||||
private:
|
||||
bool has_zero = false;
|
||||
char zero_value_storage[sizeof(Cell)]; /// Кусок памяти для элемента с ключём 0.
|
||||
|
||||
public:
|
||||
bool hasZero() const { return has_zero; }
|
||||
void setHasZero() { has_zero = true; }
|
||||
void clearHasZero() { has_zero = false; }
|
||||
|
||||
Cell * zeroValue() { return reinterpret_cast<Cell*>(zero_value_storage); }
|
||||
const Cell * zeroValue() const { return reinterpret_cast<const Cell*>(zero_value_storage); }
|
||||
};
|
||||
|
||||
template <typename Cell>
|
||||
struct ZeroValueStorage<false, Cell>
|
||||
{
|
||||
bool hasZero() const { return false; }
|
||||
void setHasZero() { throw DB::Exception("HashTable: logical error", DB::ErrorCodes::LOGICAL_ERROR); }
|
||||
void clearHasZero() {}
|
||||
|
||||
Cell * zeroValue() { return nullptr; }
|
||||
const Cell * zeroValue() const { return nullptr; }
|
||||
};
|
||||
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Cell,
|
||||
typename Hash,
|
||||
typename Grower,
|
||||
typename Allocator
|
||||
>
|
||||
class HashTable :
|
||||
private boost::noncopyable,
|
||||
protected Hash,
|
||||
protected Allocator,
|
||||
protected Cell::State,
|
||||
protected ZeroValueStorage<Cell::need_zero_value_storage, Cell> /// empty base optimization
|
||||
{
|
||||
protected:
|
||||
friend class const_iterator;
|
||||
friend class iterator;
|
||||
|
||||
typedef size_t HashValue;
|
||||
typedef HashTable<Key, Cell, Hash, Grower, Allocator> Self;
|
||||
|
||||
size_t m_size = 0; /// Количество элементов
|
||||
Cell * buf; /// Кусок памяти для всех элементов кроме элемента с ключём 0.
|
||||
Grower grower;
|
||||
|
||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
mutable size_t collisions;
|
||||
#endif
|
||||
|
||||
size_t hash(const Key & x) const { return Hash::operator()(x); }
|
||||
|
||||
/// Размер хэш-таблицы в байтах.
|
||||
size_t bufSizeBytes() const { return grower.bufSize() * sizeof(Cell); }
|
||||
|
||||
/// Найти ячейку с тем же ключём или пустую ячейку, начиная с заданного места и далее по цепочке разрешения коллизий.
|
||||
size_t findCell(const Key & x, size_t place_value) const
|
||||
{
|
||||
while (!buf[place_value].isZero(*this) && !buf[place_value].keyEquals(x))
|
||||
{
|
||||
place_value = grower.next(place_value);
|
||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
++collisions;
|
||||
#endif
|
||||
}
|
||||
|
||||
return place_value;
|
||||
}
|
||||
|
||||
void alloc()
|
||||
{
|
||||
buf = reinterpret_cast<Cell *>(Allocator::alloc(bufSizeBytes()));
|
||||
}
|
||||
|
||||
void free()
|
||||
{
|
||||
Allocator::free(buf, bufSizeBytes());
|
||||
}
|
||||
|
||||
|
||||
/// Увеличить размер буфера.
|
||||
void resize(size_t for_num_elems = 0)
|
||||
{
|
||||
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
|
||||
Stopwatch watch;
|
||||
#endif
|
||||
|
||||
size_t old_size = grower.bufSize();
|
||||
size_t old_size_bytes = bufSizeBytes();
|
||||
|
||||
if (for_num_elems)
|
||||
{
|
||||
grower.set(for_num_elems);
|
||||
if (grower.bufSize() <= old_size)
|
||||
return;
|
||||
}
|
||||
else
|
||||
grower.increaseSize();
|
||||
|
||||
/// Расширим пространство.
|
||||
buf = reinterpret_cast<Cell *>(Allocator::realloc(buf, old_size_bytes, bufSizeBytes()));
|
||||
|
||||
/** Теперь некоторые элементы может потребоваться переместить на новое место.
|
||||
* Элемент может остаться на месте, или переместиться в новое место "справа",
|
||||
* или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа".
|
||||
*/
|
||||
size_t i = 0;
|
||||
for (; i < old_size; ++i)
|
||||
if (!buf[i].isZero(*this) && !buf[i].isDeleted())
|
||||
reinsert(buf[i]);
|
||||
|
||||
/** Также имеется особый случай:
|
||||
* если элемент должен был быть в конце старого буфера, [ x]
|
||||
* но находится в начале из-за цепочки разрешения коллизий, [o x]
|
||||
* то после ресайза, он сначала снова окажется не на своём месте, [ xo ]
|
||||
* и для того, чтобы перенести его куда надо,
|
||||
* надо будет после переноса всех элементов из старой половинки [ o x ]
|
||||
* обработать ещё хвостик из цепочки разрешения коллизий сразу после неё [ o x ]
|
||||
*/
|
||||
for (; !buf[i].isZero(*this) && !buf[i].isDeleted(); ++i)
|
||||
reinsert(buf[i]);
|
||||
|
||||
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
|
||||
watch.stop();
|
||||
std::cerr << std::fixed << std::setprecision(3)
|
||||
<< "Resize from " << old_size << " to " << grower.bufSize() << " took " << watch.elapsedSeconds() << " sec."
|
||||
<< std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/** Вставить в новый буфер значение, которое было в старом буфере.
|
||||
* Используется при увеличении размера буфера.
|
||||
*/
|
||||
void reinsert(Cell & x)
|
||||
{
|
||||
size_t place_value = grower.place(x.getHash(*this));
|
||||
|
||||
/// Если элемент на своём месте.
|
||||
if (&x == &buf[place_value])
|
||||
return;
|
||||
|
||||
/// Вычисление нового места, с учётом цепочки разрешения коллизий.
|
||||
place_value = findCell(Cell::getKey(x.getValue()), place_value);
|
||||
|
||||
/// Если элемент остался на своём месте в старой цепочке разрешения коллизий.
|
||||
if (!buf[place_value].isZero(*this) && x.keyEquals(buf[place_value]))
|
||||
return;
|
||||
|
||||
/// Копирование на новое место и зануление старого.
|
||||
memcpy(&buf[place_value], &x, sizeof(x));
|
||||
x.setZero();
|
||||
|
||||
/// Потом на старое место могут переместиться элементы, которые раньше были в коллизии с этим.
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
typedef Key key_type;
|
||||
typedef typename Cell::value_type value_type;
|
||||
|
||||
|
||||
HashTable()
|
||||
{
|
||||
if (Cell::need_zero_value_storage)
|
||||
this->zeroValue()->setZero();
|
||||
alloc();
|
||||
|
||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
collisions = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
~HashTable()
|
||||
{
|
||||
if (!__has_trivial_destructor(Cell))
|
||||
for (iterator it = begin(); it != end(); ++it)
|
||||
it.ptr->~Cell();
|
||||
|
||||
free();
|
||||
}
|
||||
|
||||
|
||||
class iterator
|
||||
{
|
||||
Self * container;
|
||||
Cell * ptr;
|
||||
|
||||
friend class HashTable;
|
||||
|
||||
iterator(Self * container_, Cell * 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->isZero(*container)))
|
||||
ptr = container->buf;
|
||||
else
|
||||
++ptr;
|
||||
|
||||
while (ptr < container->buf + container->grower.bufSize() && ptr->isZero(*container))
|
||||
++ptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
value_type & operator* () const { return ptr->getValue(); }
|
||||
value_type * operator->() const { return &ptr->getValue(); }
|
||||
};
|
||||
|
||||
|
||||
class const_iterator
|
||||
{
|
||||
const Self * container;
|
||||
const Cell * ptr;
|
||||
|
||||
friend class HashTable;
|
||||
|
||||
const_iterator(const Self * container_, const Cell * 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->isZero(*container)))
|
||||
ptr = container->buf;
|
||||
else
|
||||
++ptr;
|
||||
|
||||
while (ptr < container->buf + container->grower.bufSize() && ptr->isZero(*container))
|
||||
++ptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const value_type & operator* () const { return ptr->getValue(); }
|
||||
const value_type * operator->() const { return &ptr->getValue(); }
|
||||
};
|
||||
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
if (this->hasZero())
|
||||
return const_iterator(this, this->zeroValue());
|
||||
|
||||
const Cell * ptr = buf;
|
||||
while (ptr < buf + grower.bufSize() && ptr->isZero(*this))
|
||||
++ptr;
|
||||
|
||||
return const_iterator(this, ptr);
|
||||
}
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
if (this->hasZero())
|
||||
return iterator(this, this->zeroValue());
|
||||
|
||||
Cell * ptr = buf;
|
||||
while (ptr < buf + grower.bufSize() && ptr->isZero(*this))
|
||||
++ptr;
|
||||
|
||||
return iterator(this, ptr);
|
||||
}
|
||||
|
||||
const_iterator end() const { return const_iterator(this, buf + grower.bufSize()); }
|
||||
iterator end() { return iterator(this, buf + grower.bufSize()); }
|
||||
|
||||
|
||||
protected:
|
||||
/// Если ключ нулевой - вставить его в специальное место и вернуть true.
|
||||
bool emplaceIfZero(Key x, iterator & it, bool & inserted)
|
||||
{
|
||||
if (Cell::isZero(x, *this))
|
||||
{
|
||||
if (!this->hasZero())
|
||||
{
|
||||
++m_size;
|
||||
this->setHasZero();
|
||||
inserted = true;
|
||||
}
|
||||
else
|
||||
inserted = false;
|
||||
|
||||
it = begin();
|
||||
it.ptr->setHash(hash(x));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// Только для ненулевых ключей. Найти нужное место, вставить туда ключ, если его ещё нет, вернуть итератор на ячейку.
|
||||
void emplaceNonZero(Key x, iterator & it, bool & inserted, size_t hash_value)
|
||||
{
|
||||
size_t place_value = findCell(x, grower.place(hash_value));
|
||||
|
||||
it = iterator(this, &buf[place_value]);
|
||||
|
||||
if (!buf[place_value].isZero(*this) && buf[place_value].keyEquals(x))
|
||||
{
|
||||
inserted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
new(&buf[place_value]) Cell(x, *this);
|
||||
buf[place_value].setHash(hash_value);
|
||||
inserted = true;
|
||||
++m_size;
|
||||
|
||||
if (unlikely(grower.overflow(m_size)))
|
||||
{
|
||||
resize();
|
||||
it = find(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
/// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace.
|
||||
std::pair<iterator, bool> insert(const value_type & x)
|
||||
{
|
||||
std::pair<iterator, bool> res;
|
||||
|
||||
if (!emplaceIfZero(Cell::getKey(x), res.first, res.second))
|
||||
emplaceNonZero(Cell::getKey(x), res.first, res.second, hash(Cell::getKey(x)));
|
||||
|
||||
if (res.second)
|
||||
res.first.ptr->setMapped(x);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/** Вставить ключ,
|
||||
* вернуть итератор на позицию, которую можно использовать для placement new значения,
|
||||
* а также флаг - был ли вставлен новый ключ.
|
||||
*
|
||||
* Вы обязаны сделать placement new значения, если был вставлен новый ключ,
|
||||
* так как при уничтожении хэш-таблицы для него будет вызываться деструктор!
|
||||
*
|
||||
* Пример использования:
|
||||
*
|
||||
* Map::iterator it;
|
||||
* bool inserted;
|
||||
* map.emplace(key, it, inserted);
|
||||
* if (inserted)
|
||||
* new(&it->second) Mapped(value);
|
||||
*/
|
||||
void emplace(Key x, iterator & it, bool & inserted)
|
||||
{
|
||||
if (!emplaceIfZero(x, it, inserted))
|
||||
emplaceNonZero(x, it, inserted, hash(x));
|
||||
}
|
||||
|
||||
|
||||
/// То же самое, но с заранее вычисленным значением хэш-функции.
|
||||
void emplace(Key x, iterator & it, bool & inserted, size_t hash_value)
|
||||
{
|
||||
if (!emplaceIfZero(x, it, inserted))
|
||||
emplaceNonZero(x, it, inserted, hash_value);
|
||||
}
|
||||
|
||||
|
||||
iterator find(Key x)
|
||||
{
|
||||
if (Cell::isZero(x, *this))
|
||||
return this->hasZero() ? begin() : end();
|
||||
|
||||
size_t place_value = findCell(x, grower.place(hash(x)));
|
||||
|
||||
return !buf[place_value].isZero(*this) ? iterator(this, &buf[place_value]) : end();
|
||||
}
|
||||
|
||||
|
||||
const_iterator find(Key x) const
|
||||
{
|
||||
if (Cell::isZero(x, *this))
|
||||
return this->hasZero() ? begin() : end();
|
||||
|
||||
size_t place_value = findCell(x, grower.place(hash(x)));
|
||||
|
||||
return !buf[place_value].isZero(*this) ? const_iterator(this, &buf[place_value]) : end();
|
||||
}
|
||||
|
||||
|
||||
void write(DB::WriteBuffer & wb) const
|
||||
{
|
||||
Cell::State::write(wb);
|
||||
DB::writeVarUInt(m_size, wb);
|
||||
|
||||
if (this->hasZero())
|
||||
this->zeroValue()->write(wb);
|
||||
|
||||
for (size_t i = 0; i < grower.bufSize(); ++i)
|
||||
if (!buf[i].isZero(*this))
|
||||
buf[i].write(wb);
|
||||
}
|
||||
|
||||
void writeText(DB::WriteBuffer & wb) const
|
||||
{
|
||||
Cell::State::writeText(wb);
|
||||
DB::writeText(m_size, wb);
|
||||
|
||||
if (this->hasZero())
|
||||
{
|
||||
DB::writeChar(',', wb);
|
||||
this->zeroValue()->writeText(wb);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < grower.bufSize(); ++i)
|
||||
{
|
||||
if (!buf[i].isZero(*this))
|
||||
{
|
||||
DB::writeChar(',', wb);
|
||||
buf[i].writeText(wb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void read(DB::ReadBuffer & rb)
|
||||
{
|
||||
Cell::State::read(rb);
|
||||
|
||||
this->clearHasZero();
|
||||
m_size = 0;
|
||||
|
||||
size_t new_size = 0;
|
||||
DB::readVarUInt(new_size, rb);
|
||||
|
||||
free();
|
||||
grower.set(new_size);
|
||||
alloc();
|
||||
|
||||
for (size_t i = 0; i < new_size; ++i)
|
||||
{
|
||||
Cell x;
|
||||
x.read(rb);
|
||||
insert(x);
|
||||
}
|
||||
}
|
||||
|
||||
void readText(DB::ReadBuffer & rb)
|
||||
{
|
||||
Cell::State::readText(rb);
|
||||
|
||||
this->clearHasZero();
|
||||
m_size = 0;
|
||||
|
||||
size_t new_size = 0;
|
||||
DB::readText(new_size, rb);
|
||||
|
||||
free();
|
||||
grower.set(new_size);
|
||||
alloc();
|
||||
|
||||
for (size_t i = 0; i < new_size; ++i)
|
||||
{
|
||||
Cell x;
|
||||
DB::assertString(",", rb);
|
||||
x.readText(rb);
|
||||
insert(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return 0 == m_size;
|
||||
}
|
||||
|
||||
size_t getBufferSizeInBytes() const
|
||||
{
|
||||
return bufSizeBytes();
|
||||
}
|
||||
|
||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
size_t getCollisions() const
|
||||
{
|
||||
return collisions;
|
||||
}
|
||||
#endif
|
||||
};
|
143
dbms/include/DB/Common/HashTable/HashTableAllocator.h
Normal file
143
dbms/include/DB/Common/HashTable/HashTableAllocator.h
Normal file
@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
|
||||
|
||||
/** Общая часть разных хэш-таблиц, отвечающая за выделение/освобождение памяти.
|
||||
* Используется в качестве параметра шаблона (есть несколько реализаций с таким же интерфейсом).
|
||||
*/
|
||||
class HashTableAllocator
|
||||
{
|
||||
private:
|
||||
/** Многие современные аллокаторы (например, tcmalloc) не умеют делать mremap для realloc,
|
||||
* даже в случае достаточно больших кусков памяти.
|
||||
* Хотя это позволяет увеличить производительность и уменьшить потребление памяти во время realloc-а.
|
||||
* Чтобы это исправить, делаем mremap самостоятельно, если кусок памяти достаточно большой.
|
||||
* Порог (16 МБ) выбран достаточно большим, так как изменение адресного пространства
|
||||
* довольно сильно тормозит, особенно в случае наличия большого количества потоков.
|
||||
* Рассчитываем, что набор операций mmap/что-то сделать/mremap может выполняться всего лишь около 1000 раз в секунду.
|
||||
*
|
||||
* PS. Также это требуется, потому что tcmalloc не может выделить кусок памяти больше 16 GB.
|
||||
* NOTE Можно попробовать MAP_HUGETLB, но придётся самостоятельно управлять количеством доступных страниц.
|
||||
*/
|
||||
static constexpr size_t MMAP_THRESHOLD = 16 * (1 << 20);
|
||||
|
||||
public:
|
||||
/// Выделить кусок памяти и заполнить его нулями.
|
||||
void * alloc(size_t size)
|
||||
{
|
||||
void * buf;
|
||||
|
||||
if (size >= MMAP_THRESHOLD)
|
||||
{
|
||||
buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (MAP_FAILED == buf)
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot mmap.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
/// Заполнение нулями не нужно - mmap сам это делает.
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = ::calloc(size, 1);
|
||||
if (nullptr == buf)
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot calloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/// Освободить память.
|
||||
void free(void * buf, size_t size)
|
||||
{
|
||||
if (size >= MMAP_THRESHOLD)
|
||||
{
|
||||
if (0 != munmap(buf, size))
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot munmap.", DB::ErrorCodes::CANNOT_MUNMAP);
|
||||
}
|
||||
else
|
||||
{
|
||||
::free(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/** Увеличить размер куска памяти.
|
||||
* Содержимое старого куска памяти переезжает в начало нового.
|
||||
* Оставшаяся часть заполняется нулями.
|
||||
* Положение куска памяти может измениться.
|
||||
*/
|
||||
void * realloc(void * buf, size_t old_size, size_t new_size)
|
||||
{
|
||||
if (old_size < MMAP_THRESHOLD && new_size < MMAP_THRESHOLD)
|
||||
{
|
||||
buf = ::realloc(buf, new_size);
|
||||
if (nullptr == buf)
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot realloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
||||
}
|
||||
else if (old_size >= MMAP_THRESHOLD && new_size >= MMAP_THRESHOLD)
|
||||
{
|
||||
buf = mremap(buf, old_size, new_size, MREMAP_MAYMOVE);
|
||||
if (MAP_FAILED == buf)
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot mremap.", DB::ErrorCodes::CANNOT_MREMAP);
|
||||
|
||||
/// Заполнение нулями не нужно.
|
||||
}
|
||||
else
|
||||
{
|
||||
free(buf, old_size);
|
||||
buf = alloc(new_size);
|
||||
}
|
||||
|
||||
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Аллокатор с оптимизацией для маленьких кусков памяти.
|
||||
*/
|
||||
template <size_t N = 64>
|
||||
class HashTableAllocatorWithStackMemory : private HashTableAllocator
|
||||
{
|
||||
private:
|
||||
char stack_memory[N]{};
|
||||
|
||||
public:
|
||||
void * alloc(size_t size)
|
||||
{
|
||||
if (size <= N)
|
||||
return stack_memory;
|
||||
|
||||
return HashTableAllocator::alloc(size);
|
||||
}
|
||||
|
||||
void free(void * buf, size_t size)
|
||||
{
|
||||
if (size > N)
|
||||
HashTableAllocator::free(buf, size);
|
||||
}
|
||||
|
||||
void * realloc(void * buf, size_t old_size, size_t new_size)
|
||||
{
|
||||
if (new_size <= N)
|
||||
return buf;
|
||||
|
||||
if (old_size > N)
|
||||
return HashTableAllocator::realloc(buf, old_size, new_size);
|
||||
|
||||
buf = ::malloc(new_size);
|
||||
if (nullptr == buf)
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot malloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
memcpy(buf, stack_memory, old_size);
|
||||
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
||||
|
||||
return buf;
|
||||
}
|
||||
};
|
@ -1,96 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
/** Общая часть разных хэш-таблиц, отвечающая за выделение/освобождение памяти.
|
||||
* Используется в качестве параметра шаблона (есть несколько реализаций с таким же интерфейсом).
|
||||
*/
|
||||
class HashTableAllocator
|
||||
{
|
||||
public:
|
||||
/// Выделить кусок памяти и заполнить его нулями.
|
||||
void * alloc(size_t size)
|
||||
{
|
||||
void * buf = ::calloc(size, 1);
|
||||
if (nullptr == buf)
|
||||
throwFromErrno("HashTableAllocator: Cannot calloc.", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/// Освободить память.
|
||||
void free(void * buf, size_t size)
|
||||
{
|
||||
::free(buf);
|
||||
}
|
||||
|
||||
/** Увеличить размер куска памяти.
|
||||
* Содержимое старого куска памяти переезжает в начало нового.
|
||||
* Оставшаяся часть заполняется нулями.
|
||||
* Положение куска памяти может измениться.
|
||||
*/
|
||||
void * realloc(void * buf, size_t old_size, size_t new_size)
|
||||
{
|
||||
buf = ::realloc(buf, new_size);
|
||||
if (nullptr == buf)
|
||||
throwFromErrno("HashTableAllocator: Cannot realloc.", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Аллокатор с оптимизацией для маленьких кусков памяти.
|
||||
*/
|
||||
template <size_t N = 64>
|
||||
class HashTableAllocatorWithStackMemory : private HashTableAllocator
|
||||
{
|
||||
private:
|
||||
char stack_memory[N]{};
|
||||
|
||||
public:
|
||||
void * alloc(size_t size)
|
||||
{
|
||||
if (size <= N)
|
||||
return stack_memory;
|
||||
|
||||
return HashTableAllocator::alloc(size);
|
||||
}
|
||||
|
||||
void free(void * buf, size_t size)
|
||||
{
|
||||
if (size > N)
|
||||
HashTableAllocator::free(buf, size);
|
||||
}
|
||||
|
||||
void * realloc(void * buf, size_t old_size, size_t new_size)
|
||||
{
|
||||
if (new_size <= N)
|
||||
return buf;
|
||||
|
||||
if (old_size > N)
|
||||
return HashTableAllocator::realloc(buf, old_size, new_size);
|
||||
|
||||
buf = ::malloc(new_size);
|
||||
if (nullptr == buf)
|
||||
throwFromErrno("HashTableAllocator: Cannot malloc.", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
memcpy(buf, stack_memory, old_size);
|
||||
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
||||
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
38
dbms/include/DB/Common/UInt128.h
Normal file
38
dbms/include/DB/Common/UInt128.h
Normal file
@ -0,0 +1,38 @@
|
||||
#include <DB/Common/HashTable/Hash.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
#include <DB/IO/WriteHelpers.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/// Для агрегации по SipHash или конкатенации нескольких полей.
|
||||
struct UInt128
|
||||
{
|
||||
UInt64 first;
|
||||
UInt64 second;
|
||||
|
||||
bool operator== (const UInt128 rhs) const { return first == rhs.first && second == rhs.second; }
|
||||
bool operator!= (const UInt128 rhs) const { return first != rhs.first || second != rhs.second; }
|
||||
|
||||
bool operator== (const UInt64 rhs) const { return first == rhs && second == 0; }
|
||||
bool operator!= (const UInt64 rhs) const { return first != rhs || second != 0; }
|
||||
|
||||
UInt128 & operator= (const UInt64 rhs) { first = rhs; second = 0; return *this; }
|
||||
};
|
||||
|
||||
struct UInt128Hash
|
||||
{
|
||||
DefaultHash<UInt64> hash64;
|
||||
size_t operator()(UInt128 x) const { return hash64(hash64(x.first) ^ x.second); }
|
||||
};
|
||||
|
||||
struct UInt128TrivialHash
|
||||
{
|
||||
size_t operator()(UInt128 x) const { return x.first; }
|
||||
};
|
||||
|
||||
inline void readBinary(UInt128 & x, ReadBuffer & buf) { readPODBinary(x, buf); }
|
||||
inline void writeBinary(const UInt128 & x, WriteBuffer & buf) { writePODBinary(x, buf); }
|
||||
|
||||
}
|
@ -246,6 +246,8 @@ namespace ErrorCodes
|
||||
ABORTED,
|
||||
NO_REPLICA_NAME_GIVEN,
|
||||
FORMAT_VERSION_TOO_OLD,
|
||||
CANNOT_MUNMAP,
|
||||
CANNOT_MREMAP,
|
||||
|
||||
POCO_EXCEPTION = 1000,
|
||||
STD_EXCEPTION,
|
||||
|
@ -10,93 +10,87 @@
|
||||
#include <ostream>
|
||||
|
||||
|
||||
namespace DB
|
||||
/// Штука, чтобы не создавать строки для поиска подстроки в хэш таблице.
|
||||
struct StringRef
|
||||
{
|
||||
/// Штука, чтобы не создавать строки для поиска подстроки в хэш таблице.
|
||||
struct StringRef
|
||||
{
|
||||
const char * data = nullptr;
|
||||
size_t size = 0;
|
||||
const char * data = nullptr;
|
||||
size_t size = 0;
|
||||
|
||||
StringRef(const char * data_, size_t size_) : data(data_), size(size_) {}
|
||||
StringRef(const unsigned char * data_, size_t size_) : data(reinterpret_cast<const char *>(data_)), size(size_) {}
|
||||
StringRef(const std::string & s) : data(s.data()), size(s.size()) {}
|
||||
StringRef() {}
|
||||
StringRef(const char * data_, size_t size_) : data(data_), size(size_) {}
|
||||
StringRef(const unsigned char * data_, size_t size_) : data(reinterpret_cast<const char *>(data_)), size(size_) {}
|
||||
StringRef(const std::string & s) : data(s.data()), size(s.size()) {}
|
||||
StringRef() {}
|
||||
|
||||
std::string toString() const { return std::string(data, size); }
|
||||
};
|
||||
std::string toString() const { return std::string(data, size); }
|
||||
};
|
||||
|
||||
typedef std::vector<StringRef> StringRefs;
|
||||
typedef std::vector<StringRef> StringRefs;
|
||||
|
||||
inline bool operator==(StringRef lhs, StringRef rhs)
|
||||
{
|
||||
/// Так почему-то быстрее, чем return lhs.size == rhs.size && 0 == memcmp(lhs.data, rhs.data, lhs.size);
|
||||
|
||||
if (lhs.size != rhs.size)
|
||||
inline bool operator==(StringRef lhs, StringRef rhs)
|
||||
{
|
||||
/// Так почему-то быстрее, чем return lhs.size == rhs.size && 0 == memcmp(lhs.data, rhs.data, lhs.size);
|
||||
|
||||
if (lhs.size != rhs.size)
|
||||
return false;
|
||||
|
||||
for (size_t pos = 0; pos < lhs.size; ++pos)
|
||||
if (lhs.data[pos] != rhs.data[pos])
|
||||
return false;
|
||||
|
||||
for (size_t pos = 0; pos < lhs.size; ++pos)
|
||||
if (lhs.data[pos] != rhs.data[pos])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool operator!=(StringRef lhs, StringRef rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
inline bool operator<(StringRef lhs, StringRef rhs)
|
||||
{
|
||||
int cmp = memcmp(lhs.data, rhs.data, std::min(lhs.size, rhs.size));
|
||||
if (cmp == 0)
|
||||
return lhs.size < rhs.size;
|
||||
else
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
struct StringRefHash
|
||||
{
|
||||
inline size_t operator() (StringRef x) const
|
||||
{
|
||||
return CityHash64(x.data, x.size);
|
||||
}
|
||||
};
|
||||
|
||||
struct StringRefZeroTraits
|
||||
{
|
||||
static inline bool check(DB::StringRef x) { return nullptr == x.data; }
|
||||
static inline void set(DB::StringRef & x) { x.data = nullptr; }
|
||||
};
|
||||
|
||||
inline bool operator==(StringRef lhs, const char * rhs)
|
||||
{
|
||||
for (size_t pos = 0; pos < lhs.size; ++pos)
|
||||
{
|
||||
if (!rhs[pos] || lhs.data[pos] != rhs[pos])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os, const StringRef & str)
|
||||
{
|
||||
if (str.data)
|
||||
os.write(str.data, str.size);
|
||||
|
||||
return os;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool operator!=(StringRef lhs, StringRef rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
inline bool operator<(StringRef lhs, StringRef rhs)
|
||||
{
|
||||
int cmp = memcmp(lhs.data, rhs.data, std::min(lhs.size, rhs.size));
|
||||
if (cmp == 0)
|
||||
return lhs.size < rhs.size;
|
||||
else
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
|
||||
struct StringRefHash
|
||||
{
|
||||
size_t operator() (StringRef x) const
|
||||
{
|
||||
return CityHash64(x.data, x.size);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct hash<DB::StringRef>
|
||||
{
|
||||
size_t operator()(const DB::StringRef & x) const
|
||||
{
|
||||
return CityHash64(x.data, x.size);
|
||||
}
|
||||
};
|
||||
struct hash<StringRef> : public StringRefHash {};
|
||||
}
|
||||
|
||||
|
||||
namespace ZeroTraits
|
||||
{
|
||||
inline bool check(StringRef x) { return nullptr == x.data; }
|
||||
inline void set(StringRef & x) { x.data = nullptr; }
|
||||
};
|
||||
|
||||
|
||||
inline bool operator==(StringRef lhs, const char * rhs)
|
||||
{
|
||||
for (size_t pos = 0; pos < lhs.size; ++pos)
|
||||
if (!rhs[pos] || lhs.data[pos] != rhs[pos])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os, const StringRef & str)
|
||||
{
|
||||
if (str.data)
|
||||
os.write(str.data, str.size);
|
||||
|
||||
return os;
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <Poco/Types.h>
|
||||
#include <Poco/SharedPtr.h>
|
||||
#include <Yandex/strong_typedef.h>
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
||||
#include <DB/Interpreters/HashSet.h>
|
||||
#include <DB/Common/HashTable/HashSet.h>
|
||||
#include <DB/Interpreters/AggregationCommon.h>
|
||||
#include <DB/Interpreters/Limits.h>
|
||||
|
||||
@ -147,7 +147,7 @@ private:
|
||||
size_t max_bytes;
|
||||
OverflowMode overflow_mode;
|
||||
|
||||
typedef HashSet<UInt128, UInt128Hash, UInt128ZeroTraits> SetHashed;
|
||||
typedef HashSet<UInt128, UInt128Hash> SetHashed;
|
||||
SetHashed set;
|
||||
};
|
||||
|
||||
|
@ -7,8 +7,8 @@
|
||||
#include <DB/Columns/ColumnString.h>
|
||||
|
||||
#include <DB/Functions/IFunction.h>
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
#include <DB/Interpreters/ClearableHashMap.h>
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
#include <DB/Common/HashTable/ClearableHashMap.h>
|
||||
#include <DB/Interpreters/AggregationCommon.h>
|
||||
#include <DB/Functions/NumberTraits.h>
|
||||
#include <DB/Functions/FunctionsConditional.h>
|
||||
@ -1052,19 +1052,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
struct table_growth_traits
|
||||
/// Изначально выделить кусок памяти для 512 элементов.
|
||||
struct Grower : public HashTableGrower
|
||||
{
|
||||
/// Изначально выделить кусок памяти для 512 элементов.
|
||||
static const int INITIAL_SIZE_DEGREE = 9;
|
||||
|
||||
/** Степень роста хэш таблицы, пока не превышен порог размера. (В 4 раза.)
|
||||
*/
|
||||
static const int FAST_GROWTH_DEGREE = 2;
|
||||
|
||||
/** Порог размера, после которого степень роста уменьшается (до роста в 2 раза) - 8 миллионов элементов.
|
||||
* После этого порога, максимально возможный оверхед по памяти будет всего лишь в 4, а не в 8 раз.
|
||||
*/
|
||||
static const int GROWTH_CHANGE_THRESHOLD = 23;
|
||||
static const size_t initial_size_degree = 9;
|
||||
Grower() { size_degree = initial_size_degree; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@ -1076,7 +1068,9 @@ private:
|
||||
const ColumnArray::Offsets_t & offsets = array->getOffsets();
|
||||
const typename ColumnVector<T>::Container_t & values = nested->getData();
|
||||
|
||||
typedef ClearableHashMap<T, UInt32, default_hash<T>, table_growth_traits> ValuesToIndices;
|
||||
typedef ClearableHashMap<T, UInt32, DefaultHash<T>, Grower,
|
||||
HashTableAllocatorWithStackMemory<(1 << Grower::initial_size_degree) * sizeof(T)> > ValuesToIndices;
|
||||
|
||||
ValuesToIndices indices;
|
||||
size_t prev_off = 0;
|
||||
for (size_t i = 0; i < offsets.size(); ++i)
|
||||
@ -1100,7 +1094,9 @@ private:
|
||||
const ColumnArray::Offsets_t & offsets = array->getOffsets();
|
||||
|
||||
size_t prev_off = 0;
|
||||
typedef ClearableHashMap<StringRef, UInt32, std::hash<StringRef>, table_growth_traits> ValuesToIndices;
|
||||
typedef ClearableHashMap<StringRef, UInt32, StringRefHash, Grower,
|
||||
HashTableAllocatorWithStackMemory<(1 << Grower::initial_size_degree) * sizeof(StringRef)> > ValuesToIndices;
|
||||
|
||||
ValuesToIndices indices;
|
||||
for (size_t i = 0; i < offsets.size(); ++i)
|
||||
{
|
||||
@ -1153,7 +1149,9 @@ private:
|
||||
if (keys_bytes > 16)
|
||||
return false;
|
||||
|
||||
typedef ClearableHashMap<UInt128, UInt32, UInt128TrivialHash, table_growth_traits> ValuesToIndices;
|
||||
typedef ClearableHashMap<UInt128, UInt32, UInt128Hash, Grower,
|
||||
HashTableAllocatorWithStackMemory<(1 << Grower::initial_size_degree) * sizeof(UInt128)> > ValuesToIndices;
|
||||
|
||||
ValuesToIndices indices;
|
||||
size_t prev_off = 0;
|
||||
for (size_t i = 0; i < offsets.size(); ++i)
|
||||
@ -1177,7 +1175,9 @@ private:
|
||||
{
|
||||
size_t count = columns.size();
|
||||
|
||||
typedef ClearableHashMap<UInt128, UInt32, UInt128TrivialHash, table_growth_traits> ValuesToIndices;
|
||||
typedef ClearableHashMap<UInt128, UInt32, UInt128TrivialHash, Grower,
|
||||
HashTableAllocatorWithStackMemory<(1 << Grower::initial_size_degree) * sizeof(UInt128)> > ValuesToIndices;
|
||||
|
||||
ValuesToIndices indices;
|
||||
StringRefs keys(count);
|
||||
size_t prev_off = 0;
|
||||
|
@ -615,14 +615,14 @@ public:
|
||||
|
||||
for (size_t i = 0; i < region_ids.size(); ++i)
|
||||
{
|
||||
const DB::StringRef & name_ref = dict.getRegionName(region_ids[i], language);
|
||||
const StringRef & name_ref = dict.getRegionName(region_ids[i], language);
|
||||
col_to->insertDataWithTerminatingZero(name_ref.data, name_ref.size + 1);
|
||||
}
|
||||
}
|
||||
else if (const ColumnConst<UInt32> * col_from = dynamic_cast<const ColumnConst<UInt32> *>(&*block.getByPosition(arguments[0]).column))
|
||||
{
|
||||
UInt32 region_id = col_from->getData();
|
||||
const DB::StringRef & name_ref = dict.getRegionName(region_id, language);
|
||||
const StringRef & name_ref = dict.getRegionName(region_id, language);
|
||||
|
||||
block.getByPosition(result).column = new ColumnConstString(col_from->size(), name_ref.toString());
|
||||
}
|
||||
|
@ -5,54 +5,19 @@
|
||||
|
||||
#include <DB/Common/SipHash.h>
|
||||
#include <DB/Common/Arena.h>
|
||||
#include <DB/Core/Row.h>
|
||||
#include <DB/Common/UInt128.h>
|
||||
#include <DB/Core/StringRef.h>
|
||||
#include <DB/Columns/IColumn.h>
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
|
||||
|
||||
template <>
|
||||
struct DefaultHash<StringRef> : public StringRefHash {};
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
/// Для агрегации по SipHash или конкатенации нескольких полей.
|
||||
struct UInt128
|
||||
{
|
||||
UInt64 first;
|
||||
UInt64 second;
|
||||
|
||||
bool operator== (const UInt128 rhs) const { return first == rhs.first && second == rhs.second; }
|
||||
bool operator!= (const UInt128 rhs) const { return first != rhs.first || second != rhs.second; }
|
||||
};
|
||||
|
||||
struct UInt128Hash
|
||||
{
|
||||
default_hash<UInt64> hash64;
|
||||
size_t operator()(UInt128 x) const { return hash64(hash64(x.first) ^ x.second); }
|
||||
};
|
||||
|
||||
struct UInt128TrivialHash
|
||||
{
|
||||
size_t operator()(UInt128 x) const { return x.first; }
|
||||
};
|
||||
|
||||
struct UInt128ZeroTraits
|
||||
{
|
||||
static inline bool check(UInt128 x) { return x.first == 0 && x.second == 0; }
|
||||
static inline void set(UInt128 & x) { x.first = 0; x.second = 0; }
|
||||
};
|
||||
|
||||
inline void readBinary(UInt128 & x, ReadBuffer & buf) { readPODBinary(x, buf); }
|
||||
inline void writeBinary(const UInt128 & x, WriteBuffer & buf) { writePODBinary(x, buf); }
|
||||
|
||||
|
||||
/// Немного быстрее стандартного
|
||||
struct StringHash
|
||||
{
|
||||
size_t operator()(const String & x) const { return CityHash64(x.data(), x.size()); }
|
||||
};
|
||||
|
||||
|
||||
typedef std::vector<size_t> Sizes;
|
||||
|
||||
|
||||
|
@ -43,9 +43,9 @@ typedef std::vector<AggregateDescription> AggregateDescriptions;
|
||||
*/
|
||||
typedef AggregateDataPtr AggregatedDataWithoutKey;
|
||||
typedef HashMap<UInt64, AggregateDataPtr> AggregatedDataWithUInt64Key;
|
||||
typedef HashMap<StringRef, AggregateDataPtr, StringRefHash, StringRefZeroTraits> AggregatedDataWithStringKey;
|
||||
typedef HashMap<UInt128, AggregateDataPtr, UInt128Hash, UInt128ZeroTraits> AggregatedDataWithKeys128;
|
||||
typedef HashMap<UInt128, std::pair<StringRef*, AggregateDataPtr>, UInt128TrivialHash, UInt128ZeroTraits> AggregatedDataHashed;
|
||||
typedef HashMap<StringRef, AggregateDataPtr> AggregatedDataWithStringKey;
|
||||
typedef HashMap<UInt128, AggregateDataPtr, UInt128Hash> AggregatedDataWithKeys128;
|
||||
typedef HashMap<UInt128, std::pair<StringRef*, AggregateDataPtr>, UInt128TrivialHash> AggregatedDataHashed;
|
||||
|
||||
class Aggregator;
|
||||
|
||||
@ -79,23 +79,23 @@ struct AggregatedDataVariants : private boost::noncopyable
|
||||
AggregatedDataWithoutKey without_key = nullptr;
|
||||
|
||||
/// Специализация для случая, когда есть один числовой ключ.
|
||||
/// auto_ptr - для ленивой инициализации (так как иначе HashMap в конструкторе выделяет и зануляет слишком много памяти).
|
||||
std::auto_ptr<AggregatedDataWithUInt64Key> key64;
|
||||
/// unique_ptr - для ленивой инициализации (так как иначе HashMap в конструкторе выделяет и зануляет слишком много памяти).
|
||||
std::unique_ptr<AggregatedDataWithUInt64Key> key64;
|
||||
|
||||
/// Специализация для случая, когда есть один строковый ключ.
|
||||
std::auto_ptr<AggregatedDataWithStringKey> key_string;
|
||||
std::unique_ptr<AggregatedDataWithStringKey> key_string;
|
||||
Arena string_pool;
|
||||
|
||||
size_t keys_size; /// Количество ключей
|
||||
Sizes key_sizes; /// Размеры ключей, если ключи фиксированной длины
|
||||
|
||||
/// Специализация для случая, когда ключи фискированной длины помещаются в 128 бит.
|
||||
std::auto_ptr<AggregatedDataWithKeys128> keys128;
|
||||
std::unique_ptr<AggregatedDataWithKeys128> keys128;
|
||||
|
||||
/** Агрегирует по 128 битному хэшу от ключа.
|
||||
* (При этом, строки, содержащие нули посередине, могут склеиться.)
|
||||
*/
|
||||
std::auto_ptr<AggregatedDataHashed> hashed;
|
||||
std::unique_ptr<AggregatedDataHashed> hashed;
|
||||
Arena keys_pool;
|
||||
|
||||
enum Type
|
||||
|
@ -1,224 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <malloc.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <Yandex/likely.h>
|
||||
|
||||
#include <stats/IntHash.h>
|
||||
|
||||
#include <DB/Core/Types.h>
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
/** Хеш-таблица, позволяющая очищать таблицу за O(1).
|
||||
* Еще более простая, чем HashMap: Key и Mapped должны быть POD-типами.
|
||||
*
|
||||
* Вместо этого класса можно было бы просто использовать в HashMap в качестве ключа пару <версия, ключ>,
|
||||
* но тогда таблица накапливала бы все ключи, которые в нее когда-либо складывали, и неоправданно росла.
|
||||
* Этот класс идет на шаг дальше и считает ключи со старой версией пустыми местами в хеш-таблице.
|
||||
*/
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Mapped,
|
||||
typename Hash = default_hash<Key>,
|
||||
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<Value *>(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<Value *>(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
|
||||
};
|
||||
|
||||
}
|
@ -1,614 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <malloc.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <Yandex/likely.h>
|
||||
|
||||
#include <stats/IntHash.h>
|
||||
|
||||
#include <DB/Core/Types.h>
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
|
||||
#include <DB/Common/HashTableAllocator.h>
|
||||
|
||||
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <statdaemons/Stopwatch.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
/** Очень простая хэш-таблица. Предназначена для быстрой агрегации. Есть только необходимый минимум возможностей.
|
||||
* Требования:
|
||||
* - Key и Mapped - position independent типы (для перемещения значений которых достаточно сделать memcpy).
|
||||
*
|
||||
* Желательно, чтобы Key был числом, или маленьким агрегатом (типа UInt128).
|
||||
*
|
||||
* Сценарий работы:
|
||||
* - вставлять в хэш-таблицу значения;
|
||||
* - проитерироваться по имеющимся в ней значениям.
|
||||
*
|
||||
* Open addressing.
|
||||
* Linear probing (подходит, если хэш функция хорошая!).
|
||||
* Значение с нулевым ключём хранится отдельно.
|
||||
* Удаления элементов нет.
|
||||
*/
|
||||
|
||||
|
||||
/** Хэш функции, которые лучше чем тривиальная функция std::hash.
|
||||
* (при агрегации по идентификатору посетителя, прирост производительности более чем в 5 раз)
|
||||
*/
|
||||
template <typename T> struct default_hash;
|
||||
|
||||
template <typename T>
|
||||
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<T>\
|
||||
{\
|
||||
size_t operator() (T key) const\
|
||||
{\
|
||||
return default_hash_64<T>(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 <typename T> 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<Key>,
|
||||
typename ZeroTraits = default_zero_traits<Key>,
|
||||
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<Key, Mapped> Value; /// Без const Key для простоты.
|
||||
typedef size_t HashValue;
|
||||
typedef HashMap<Key, Mapped, Hash, ZeroTraits, GrowthTraits, Allocator> 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<Value*>(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<Value *>(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<Value *>(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<iterator, bool> 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
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -1,510 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
|
||||
#include <DB/IO/WriteBuffer.h>
|
||||
#include <DB/IO/WriteHelpers.h>
|
||||
#include <DB/IO/ReadBuffer.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
#include <DB/IO/VarInt.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
/** См. HashMap.h
|
||||
*/
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Hash = default_hash<Key>,
|
||||
typename ZeroTraits = default_zero_traits<Key>,
|
||||
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<Key, Hash, ZeroTraits, GrowthTraits, Allocator> 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<Key *>(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<Key *>(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<iterator, bool> 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<int>(log2(new_size - 1)) + 2)
|
||||
? GrowthTraits::INITIAL_SIZE_DEGREE
|
||||
: (static_cast<int>(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<int>(log2(new_size - 1)) + 2)
|
||||
? GrowthTraits::INITIAL_SIZE_DEGREE
|
||||
: (static_cast<int>(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<Key, Hash, ZeroTraits, GrowthTraits, Allocator>::zero_value{};
|
||||
|
||||
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include <DB/Parsers/IAST.h>
|
||||
|
||||
#include <DB/Interpreters/HashSet.h>
|
||||
#include <DB/Common/HashTable/HashSet.h>
|
||||
#include <DB/Interpreters/AggregationCommon.h>
|
||||
#include <DB/Interpreters/Limits.h>
|
||||
#include <DB/Columns/ColumnConst.h>
|
||||
@ -33,7 +33,7 @@ class Set
|
||||
{
|
||||
public:
|
||||
Set(const Limits & limits)
|
||||
: type(EMPTY), log(&Logger::get("Set")),
|
||||
: log(&Logger::get("Set")),
|
||||
max_rows(limits.max_rows_in_set),
|
||||
max_bytes(limits.max_bytes_in_set),
|
||||
overflow_mode(limits.set_overflow_mode)
|
||||
@ -94,16 +94,16 @@ private:
|
||||
* одного или нескольких столбцов значений множеству.
|
||||
*/
|
||||
typedef HashSet<UInt64> SetUInt64;
|
||||
typedef HashSet<StringRef, StringRefHash, StringRefZeroTraits> SetString;
|
||||
typedef HashSet<UInt128, UInt128Hash, UInt128ZeroTraits> SetHashed;
|
||||
typedef HashSet<StringRef> SetString;
|
||||
typedef HashSet<UInt128, UInt128Hash> SetHashed;
|
||||
|
||||
BlockInputStreamPtr source;
|
||||
|
||||
/// Специализация для случая, когда есть один числовой ключ.
|
||||
SetUInt64 key64;
|
||||
std::unique_ptr<SetUInt64> key64;
|
||||
|
||||
/// Специализация для случая, когда есть один строковый ключ.
|
||||
SetString key_string;
|
||||
std::unique_ptr<SetString> key_string;
|
||||
Arena string_pool;
|
||||
|
||||
/** Сравнивает 128 битные хэши.
|
||||
@ -111,7 +111,7 @@ private:
|
||||
* Иначе - вычисляет SipHash от набора из всех ключей.
|
||||
* (При этом, строки, содержащие нули посередине, могут склеиться.)
|
||||
*/
|
||||
SetHashed hashed;
|
||||
std::unique_ptr<SetHashed> hashed;
|
||||
|
||||
enum Type
|
||||
{
|
||||
@ -120,7 +120,7 @@ private:
|
||||
KEY_STRING = 2,
|
||||
HASHED = 3,
|
||||
};
|
||||
Type type;
|
||||
Type type = EMPTY;
|
||||
|
||||
bool keys_fit_128_bits;
|
||||
Sizes key_sizes;
|
||||
@ -139,6 +139,22 @@ private:
|
||||
|
||||
static Type chooseMethod(const ConstColumnPlainPtrs & key_columns, bool & keys_fit_128_bits, Sizes & key_sizes);
|
||||
|
||||
void init(Type type_)
|
||||
{
|
||||
type = type_;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case EMPTY: break;
|
||||
case KEY_64: key64 .reset(new SetUInt64); break;
|
||||
case KEY_STRING: key_string .reset(new SetString); break;
|
||||
case HASHED: hashed .reset(new SetHashed); break;
|
||||
|
||||
default:
|
||||
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
|
||||
}
|
||||
}
|
||||
|
||||
/// Если в левой части IN стоит массив. Проверяем, что хоть один элемент массива лежит в множестве.
|
||||
void executeConstArray(const ColumnConstArray * key_column, ColumnUInt8::Container_t & vec_res, bool negative) const;
|
||||
void executeArray(const ColumnArray * key_column, ColumnUInt8::Container_t & vec_res, bool negative) const;
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <map>
|
||||
|
||||
#include <DB/Core/Field.h>
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
#include <DB/Common/AutoArray.h>
|
||||
#include <DB/IO/WriteHelpers.h>
|
||||
|
||||
@ -155,7 +155,7 @@ int main(int argc, char ** argv)
|
||||
if (argc == 2 && !strcmp(argv[1], "1"))
|
||||
{
|
||||
typedef DB::AutoArray<T> Arr;
|
||||
typedef DB::HashMap<UInt64, Arr> Map;
|
||||
typedef HashMap<UInt64, Arr> Map;
|
||||
|
||||
Stopwatch watch;
|
||||
|
||||
@ -185,7 +185,7 @@ int main(int argc, char ** argv)
|
||||
if (argc == 2 && !strcmp(argv[1], "2"))
|
||||
{
|
||||
typedef std::vector<T> Arr;
|
||||
typedef DB::HashMap<UInt64, Arr> Map;
|
||||
typedef HashMap<UInt64, Arr> Map;
|
||||
|
||||
Stopwatch watch;
|
||||
|
||||
|
82
dbms/src/Common/tests/hash_table.cpp
Normal file
82
dbms/src/Common/tests/hash_table.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <DB/Interpreters/AggregationCommon.h>
|
||||
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
#include <DB/Common/HashTable/HashSet.h>
|
||||
|
||||
|
||||
|
||||
struct SmallGrower : public HashTableGrower
|
||||
{
|
||||
SmallGrower() { size_degree = 1; }
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
{
|
||||
typedef HashSet<int, DefaultHash<int>, SmallGrower> Cont;
|
||||
Cont cont;
|
||||
|
||||
cont.insert(1);
|
||||
cont.insert(2);
|
||||
|
||||
Cont::iterator it;
|
||||
bool inserted;
|
||||
|
||||
cont.emplace(3, it, inserted);
|
||||
std::cerr << inserted << ", " << *it << std::endl;
|
||||
|
||||
cont.emplace(3, it, inserted);
|
||||
std::cerr << inserted << ", " << *it << std::endl;
|
||||
|
||||
for (auto x : cont)
|
||||
std::cerr << x << std::endl;
|
||||
|
||||
std::string dump;
|
||||
{
|
||||
DB::WriteBufferFromString wb(dump);
|
||||
cont.writeText(wb);
|
||||
}
|
||||
|
||||
std::cerr << "dump: " << dump << std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
typedef HashMap<int, std::string, DefaultHash<int>, SmallGrower> Cont;
|
||||
Cont cont;
|
||||
|
||||
cont.insert(std::make_pair(1, "Hello, world!"));
|
||||
cont[1] = "Goodbye.";
|
||||
|
||||
for (auto x : cont)
|
||||
std::cerr << x.first << " -> " << x.second << std::endl;
|
||||
|
||||
std::string dump;
|
||||
{
|
||||
DB::WriteBufferFromString wb(dump);
|
||||
cont.writeText(wb);
|
||||
}
|
||||
|
||||
std::cerr << "dump: " << dump << std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
typedef HashSet<
|
||||
DB::UInt128,
|
||||
DB::UInt128TrivialHash> Cont;
|
||||
Cont cont;
|
||||
|
||||
std::string dump;
|
||||
{
|
||||
DB::WriteBufferFromString wb(dump);
|
||||
cont.write(wb);
|
||||
}
|
||||
|
||||
std::cerr << "dump: " << dump << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -13,14 +13,7 @@
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
|
||||
//#define DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
|
||||
struct StringRefZeroTraits
|
||||
{
|
||||
static inline bool check(DB::StringRef x) { return 0 == x.data; }
|
||||
static inline void set(DB::StringRef & x) { x.data = 0; }
|
||||
};
|
||||
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
@ -33,10 +26,10 @@ int main(int argc, char ** argv)
|
||||
|
||||
typedef std::vector<std::string> Vec;
|
||||
typedef std::unordered_map<std::string, int> Set;
|
||||
typedef std::unordered_map<DB::StringRef, int, DB::StringRefHash> RefsSet;
|
||||
typedef std::unordered_map<StringRef, int, StringRefHash> RefsSet;
|
||||
typedef google::dense_hash_map<std::string, int> DenseSet;
|
||||
typedef google::dense_hash_map<DB::StringRef, int, DB::StringRefHash> RefsDenseSet;
|
||||
typedef DB::HashMap<DB::StringRef, int, DB::StringRefHash, StringRefZeroTraits> RefsHashMap;
|
||||
typedef google::dense_hash_map<StringRef, int, StringRefHash> RefsDenseSet;
|
||||
typedef HashMap<StringRef, int, StringRefHash> RefsHashMap;
|
||||
Vec vec;
|
||||
|
||||
vec.reserve(n);
|
||||
@ -104,7 +97,7 @@ int main(int argc, char ** argv)
|
||||
Stopwatch watch;
|
||||
|
||||
for (Vec::iterator it = vec.begin(); it != vec.end(); ++it)
|
||||
set[DB::StringRef(*it)] = 0;
|
||||
set[StringRef(*it)] = 0;
|
||||
|
||||
std::cerr << "Inserted refs into std::unordered_map in " << watch.elapsedSeconds() << " sec, "
|
||||
<< vec.size() / watch.elapsedSeconds() << " rows/sec., "
|
||||
@ -125,7 +118,7 @@ int main(int argc, char ** argv)
|
||||
Stopwatch watch;
|
||||
|
||||
for (Vec::iterator it = vec.begin(); it != vec.end(); ++it)
|
||||
set[DB::StringRef(pool.insert(it->data(), it->size()), it->size())] = 0;
|
||||
set[StringRef(pool.insert(it->data(), it->size()), it->size())] = 0;
|
||||
|
||||
std::cerr << "Inserted into pool and refs into std::unordered_map in " << watch.elapsedSeconds() << " sec, "
|
||||
<< vec.size() / watch.elapsedSeconds() << " rows/sec., "
|
||||
@ -167,7 +160,7 @@ int main(int argc, char ** argv)
|
||||
Stopwatch watch;
|
||||
|
||||
for (Vec::iterator it = vec.begin(); it != vec.end(); ++it)
|
||||
set[DB::StringRef(it->data(), it->size())] = 0;
|
||||
set[StringRef(it->data(), it->size())] = 0;
|
||||
|
||||
std::cerr << "Inserted refs into google::dense_hash_map in " << watch.elapsedSeconds() << " sec, "
|
||||
<< vec.size() / watch.elapsedSeconds() << " rows/sec., "
|
||||
@ -189,7 +182,7 @@ int main(int argc, char ** argv)
|
||||
Stopwatch watch;
|
||||
|
||||
for (Vec::iterator it = vec.begin(); it != vec.end(); ++it)
|
||||
set[DB::StringRef(pool.insert(it->data(), it->size()), it->size())] = 0;
|
||||
set[StringRef(pool.insert(it->data(), it->size()), it->size())] = 0;
|
||||
|
||||
std::cerr << "Inserted into pool and refs into google::dense_hash_map in " << watch.elapsedSeconds() << " sec, "
|
||||
<< vec.size() / watch.elapsedSeconds() << " rows/sec., "
|
||||
@ -212,10 +205,10 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
RefsHashMap::iterator inserted_it;
|
||||
bool inserted;
|
||||
set.emplace(DB::StringRef(*it), inserted_it, inserted);
|
||||
set.emplace(StringRef(*it), inserted_it, inserted);
|
||||
}
|
||||
|
||||
std::cerr << "Inserted refs into DB::HashMap in " << watch.elapsedSeconds() << " sec, "
|
||||
std::cerr << "Inserted refs into HashMap in " << watch.elapsedSeconds() << " sec, "
|
||||
<< vec.size() / watch.elapsedSeconds() << " rows/sec., "
|
||||
<< in.count() / watch.elapsedSeconds() / 1000000 << " MB/sec."
|
||||
<< std::endl;
|
||||
@ -239,10 +232,10 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
RefsHashMap::iterator inserted_it;
|
||||
bool inserted;
|
||||
set.emplace(DB::StringRef(pool.insert(it->data(), it->size()), it->size()), inserted_it, inserted);
|
||||
set.emplace(StringRef(pool.insert(it->data(), it->size()), it->size()), inserted_it, inserted);
|
||||
}
|
||||
|
||||
std::cerr << "Inserted into pool and refs into DB::HashMap in " << watch.elapsedSeconds() << " sec, "
|
||||
std::cerr << "Inserted into pool and refs into HashMap in " << watch.elapsedSeconds() << " sec, "
|
||||
<< vec.size() / watch.elapsedSeconds() << " rows/sec., "
|
||||
<< in.count() / watch.elapsedSeconds() / 1000000 << " MB/sec."
|
||||
<< std::endl;
|
||||
|
@ -24,9 +24,12 @@ namespace DB
|
||||
size_t Set::getTotalRowCount() const
|
||||
{
|
||||
size_t rows = 0;
|
||||
rows += key64.size();
|
||||
rows += key_string.size();
|
||||
rows += hashed.size();
|
||||
if (key64)
|
||||
rows += key64->size();
|
||||
if (key_string)
|
||||
rows += key_string->size();
|
||||
if (hashed)
|
||||
rows += hashed->size();
|
||||
return rows;
|
||||
}
|
||||
|
||||
@ -34,9 +37,12 @@ size_t Set::getTotalRowCount() const
|
||||
size_t Set::getTotalByteCount() const
|
||||
{
|
||||
size_t bytes = 0;
|
||||
bytes += key64.getBufferSizeInBytes();
|
||||
bytes += key_string.getBufferSizeInBytes();
|
||||
bytes += hashed.getBufferSizeInBytes();
|
||||
if (key64)
|
||||
bytes += key64->getBufferSizeInBytes();
|
||||
if (key_string)
|
||||
bytes += key_string->getBufferSizeInBytes();
|
||||
if (hashed)
|
||||
bytes += hashed->getBufferSizeInBytes();
|
||||
bytes += string_pool.size();
|
||||
return bytes;
|
||||
}
|
||||
@ -105,11 +111,11 @@ bool Set::insertFromBlock(Block & block, bool create_ordered_set)
|
||||
|
||||
/// Какую структуру данных для множества использовать?
|
||||
keys_fit_128_bits = false;
|
||||
type = chooseMethod(key_columns, keys_fit_128_bits, key_sizes);
|
||||
init(chooseMethod(key_columns, keys_fit_128_bits, key_sizes));
|
||||
|
||||
if (type == KEY_64)
|
||||
{
|
||||
SetUInt64 & res = key64;
|
||||
SetUInt64 & res = *key64;
|
||||
const IColumn & column = *key_columns[0];
|
||||
|
||||
/// Для всех строчек
|
||||
@ -125,7 +131,7 @@ bool Set::insertFromBlock(Block & block, bool create_ordered_set)
|
||||
}
|
||||
else if (type == KEY_STRING)
|
||||
{
|
||||
SetString & res = key_string;
|
||||
SetString & res = *key_string;
|
||||
const IColumn & column = *key_columns[0];
|
||||
|
||||
if (const ColumnString * column_string = dynamic_cast<const ColumnString *>(&column))
|
||||
@ -177,7 +183,7 @@ bool Set::insertFromBlock(Block & block, bool create_ordered_set)
|
||||
}
|
||||
else if (type == HASHED)
|
||||
{
|
||||
SetHashed & res = hashed;
|
||||
SetHashed & res = *hashed;
|
||||
|
||||
/// Для всех строчек
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
@ -329,7 +335,7 @@ void Set::executeOrdinary(const ConstColumnPlainPtrs & key_columns, ColumnUInt8:
|
||||
|
||||
if (type == KEY_64)
|
||||
{
|
||||
const SetUInt64 & set = key64;
|
||||
const SetUInt64 & set = *key64;
|
||||
const IColumn & column = *key_columns[0];
|
||||
|
||||
/// Для всех строчек
|
||||
@ -342,7 +348,7 @@ void Set::executeOrdinary(const ConstColumnPlainPtrs & key_columns, ColumnUInt8:
|
||||
}
|
||||
else if (type == KEY_STRING)
|
||||
{
|
||||
const SetString & set = key_string;
|
||||
const SetString & set = *key_string;
|
||||
const IColumn & column = *key_columns[0];
|
||||
|
||||
if (const ColumnString * column_string = dynamic_cast<const ColumnString *>(&column))
|
||||
@ -384,7 +390,7 @@ void Set::executeOrdinary(const ConstColumnPlainPtrs & key_columns, ColumnUInt8:
|
||||
}
|
||||
else if (type == HASHED)
|
||||
{
|
||||
const SetHashed & set = hashed;
|
||||
const SetHashed & set = *hashed;
|
||||
|
||||
/// Для всех строчек
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
@ -402,7 +408,7 @@ void Set::executeArray(const ColumnArray * key_column, ColumnUInt8::Container_t
|
||||
|
||||
if (type == KEY_64)
|
||||
{
|
||||
const SetUInt64 & set = key64;
|
||||
const SetUInt64 & set = *key64;
|
||||
|
||||
size_t prev_offset = 0;
|
||||
/// Для всех строчек
|
||||
@ -424,7 +430,7 @@ void Set::executeArray(const ColumnArray * key_column, ColumnUInt8::Container_t
|
||||
}
|
||||
else if (type == KEY_STRING)
|
||||
{
|
||||
const SetString & set = key_string;
|
||||
const SetString & set = *key_string;
|
||||
|
||||
if (const ColumnString * column_string = dynamic_cast<const ColumnString *>(&nested_column))
|
||||
{
|
||||
@ -479,7 +485,7 @@ void Set::executeArray(const ColumnArray * key_column, ColumnUInt8::Container_t
|
||||
}
|
||||
else if (type == HASHED)
|
||||
{
|
||||
const SetHashed & set = hashed;
|
||||
const SetHashed & set = *hashed;
|
||||
ConstColumnPlainPtrs nested_columns(1, &nested_column);
|
||||
|
||||
size_t prev_offset = 0;
|
||||
@ -521,13 +527,13 @@ void Set::executeConstArray(const ColumnConstArray * key_column, ColumnUInt8::Co
|
||||
{
|
||||
if (type == KEY_64)
|
||||
{
|
||||
const SetUInt64 & set = key64;
|
||||
const SetUInt64 & set = *key64;
|
||||
UInt64 key = get<UInt64>(values[j]);
|
||||
res |= negative ^ (set.end() != set.find(key));
|
||||
}
|
||||
else if (type == KEY_STRING)
|
||||
{
|
||||
const SetString & set = key_string;
|
||||
const SetString & set = *key_string;
|
||||
res |= negative ^ (set.end() != set.find(StringRef(get<String>(values[j]))));
|
||||
}
|
||||
else
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include <DB/Core/Types.h>
|
||||
#include <DB/IO/ReadBufferFromFile.h>
|
||||
#include <DB/IO/CompressedReadBuffer.h>
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
#include <DB/AggregateFunctions/IAggregateFunction.h>
|
||||
#include <DB/AggregateFunctions/AggregateFunctionFactory.h>
|
||||
#include <DB/DataTypes/DataTypesNumberFixed.h>
|
||||
@ -32,14 +32,14 @@
|
||||
*
|
||||
* То есть, тест также позволяет сравнить DB::AutoArray и std::vector.
|
||||
*
|
||||
* Если USE_AUTO_ARRAY = 0, то DB::HashMap уверенно обгоняет всех.
|
||||
* Если USE_AUTO_ARRAY = 1, то DB::HashMap чуть менее серьёзно (20%) обгоняет google::dense_hash_map.
|
||||
* Если USE_AUTO_ARRAY = 0, то HashMap уверенно обгоняет всех.
|
||||
* Если USE_AUTO_ARRAY = 1, то HashMap чуть менее серьёзно (20%) обгоняет google::dense_hash_map.
|
||||
*
|
||||
* При использовании DB::HashMap, AutoArray имеет довольно серьёзное (40%) преимущество перед std::vector.
|
||||
* При использовании HashMap, AutoArray имеет довольно серьёзное (40%) преимущество перед std::vector.
|
||||
* А при использовании других хэш-таблиц, AutoArray ещё более серьёзно обгоняет std::vector
|
||||
* (до трёх c половиной раз в случае std::unordered_map и google::sparse_hash_map).
|
||||
*
|
||||
* DB::HashMap, в отличие от google::dense_hash_map, гораздо больше зависит от качества хэш-функции.
|
||||
* HashMap, в отличие от google::dense_hash_map, гораздо больше зависит от качества хэш-функции.
|
||||
*
|
||||
* PS. Измеряйте всё сами, а то я почти запутался.
|
||||
*
|
||||
@ -122,8 +122,8 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
DB::HashMap<Key, Value> map;
|
||||
DB::HashMap<Key, Value>::iterator it;
|
||||
HashMap<Key, Value> map;
|
||||
HashMap<Key, Value>::iterator it;
|
||||
bool inserted;
|
||||
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
@ -138,7 +138,7 @@ int main(int argc, char ** argv)
|
||||
|
||||
watch.stop();
|
||||
std::cerr << std::fixed << std::setprecision(2)
|
||||
<< "DB::HashMap. Size: " << map.size()
|
||||
<< "HashMap. Size: " << map.size()
|
||||
<< ", elapsed: " << watch.elapsedSeconds()
|
||||
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
@ -151,8 +151,8 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
std::unordered_map<Key, Value, DB::default_hash<Key> > map;
|
||||
std::unordered_map<Key, Value, DB::default_hash<Key> >::iterator it;
|
||||
std::unordered_map<Key, Value, DefaultHash<Key> > map;
|
||||
std::unordered_map<Key, Value, DefaultHash<Key> >::iterator it;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
it = map.insert(std::make_pair(data[i], std::move(value))).first;
|
||||
@ -171,8 +171,8 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
google::dense_hash_map<Key, Value, DB::default_hash<Key> > map;
|
||||
google::dense_hash_map<Key, Value, DB::default_hash<Key> >::iterator it;
|
||||
google::dense_hash_map<Key, Value, DefaultHash<Key> > map;
|
||||
google::dense_hash_map<Key, Value, DefaultHash<Key> >::iterator it;
|
||||
map.set_empty_key(-1ULL);
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
@ -192,8 +192,8 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
google::sparse_hash_map<Key, Value, DB::default_hash<Key> > map;
|
||||
google::sparse_hash_map<Key, Value, DB::default_hash<Key> >::iterator it;
|
||||
google::sparse_hash_map<Key, Value, DefaultHash<Key> > map;
|
||||
google::sparse_hash_map<Key, Value, DefaultHash<Key> >::iterator it;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
map.insert(std::make_pair(data[i], std::move(value)));
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include <DB/Core/Types.h>
|
||||
#include <DB/IO/ReadBufferFromFile.h>
|
||||
#include <DB/IO/CompressedReadBuffer.h>
|
||||
#include <DB/Interpreters/HashMap.h>
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
@ -47,8 +47,8 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
DB::HashMap<Key, Value> map;
|
||||
DB::HashMap<Key, Value>::iterator it;
|
||||
HashMap<Key, Value> map;
|
||||
HashMap<Key, Value>::iterator it;
|
||||
bool inserted;
|
||||
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
@ -61,7 +61,7 @@ int main(int argc, char ** argv)
|
||||
|
||||
watch.stop();
|
||||
std::cerr << std::fixed << std::setprecision(2)
|
||||
<< "DB::HashMap. Size: " << map.size()
|
||||
<< "HashMap. Size: " << map.size()
|
||||
<< ", elapsed: " << watch.elapsedSeconds()
|
||||
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
||||
<< ", collisions: " << map.getCollisions()
|
||||
@ -71,7 +71,7 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
std::unordered_map<Key, Value, DB::default_hash<Key> > map;
|
||||
std::unordered_map<Key, Value, DefaultHash<Key> > map;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
++map[data[i]];
|
||||
|
||||
@ -86,7 +86,7 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
google::dense_hash_map<Key, Value, DB::default_hash<Key> > map;
|
||||
google::dense_hash_map<Key, Value, DefaultHash<Key> > map;
|
||||
map.set_empty_key(-1ULL);
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
++map[data[i]];
|
||||
@ -102,7 +102,7 @@ int main(int argc, char ** argv)
|
||||
{
|
||||
Stopwatch watch;
|
||||
|
||||
google::sparse_hash_map<Key, Value, DB::default_hash<Key> > map;
|
||||
google::sparse_hash_map<Key, Value, DefaultHash<Key> > map;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
++map[data[i]];
|
||||
|
||||
|
@ -7,16 +7,9 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <malloc.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <Yandex/likely.h>
|
||||
|
||||
#include <stats/IntHash.h>
|
||||
|
||||
#include <DB/Core/Types.h>
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
@ -25,531 +18,68 @@
|
||||
|
||||
#include <DB/Core/StringRef.h>
|
||||
|
||||
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <statdaemons/Stopwatch.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
/** Очень простая хэш-таблица. Предназначена для быстрой агрегации. Есть только необходимый минимум возможностей.
|
||||
* Требования:
|
||||
* - Key и Mapped - position independent типы (для перемещения значений которых достаточно сделать memcpy).
|
||||
*
|
||||
* Желательно, чтобы Key был числом, или маленьким агрегатом (типа UInt128).
|
||||
*
|
||||
* Сценарий работы:
|
||||
* - вставлять в хэш-таблицу значения;
|
||||
* - проитерироваться по имеющимся в ней значениям.
|
||||
*
|
||||
* Open addressing.
|
||||
* Linear probing (подходит, если хэш функция хорошая!).
|
||||
* Значение с нулевым ключём хранится отдельно.
|
||||
* Удаления элементов нет.
|
||||
*/
|
||||
|
||||
|
||||
/** Хэш функции, которые лучше чем тривиальная функция std::hash.
|
||||
* (при агрегации по идентификатору посетителя, прирост производительности более чем в 5 раз)
|
||||
*/
|
||||
template <typename T> struct default_hash;
|
||||
|
||||
template <typename T>
|
||||
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<T>\
|
||||
{\
|
||||
size_t operator() (T key) const\
|
||||
{\
|
||||
return default_hash_64<T>(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 <typename T> 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;
|
||||
};
|
||||
#include <DB/Common/HashTable/HashMap.h>
|
||||
|
||||
|
||||
template
|
||||
<
|
||||
typename Key,
|
||||
typename Mapped,
|
||||
typename Hash,
|
||||
typename ZeroTraits,
|
||||
typename GrowthTraits,
|
||||
typename Allocator
|
||||
typename Hash = DefaultHash<Key>,
|
||||
typename Grower = HashTableGrower,
|
||||
typename Allocator = HashTableAllocator
|
||||
>
|
||||
class HashMap : private Allocator, private Hash /// empty base optimization
|
||||
class HashMapWithDump : public HashMap<Key, Mapped, Hash, Grower, Allocator>
|
||||
{
|
||||
private:
|
||||
friend class const_iterator;
|
||||
friend class iterator;
|
||||
|
||||
typedef std::pair<Key, Mapped> Value; /// Без const Key для простоты.
|
||||
typedef size_t HashValue;
|
||||
typedef HashMap<Key, Mapped, Hash, ZeroTraits, GrowthTraits, Allocator> 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<Value*>(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<Value *>(Allocator::realloc(buf, old_size_bytes, buf_size_bytes()));
|
||||
|
||||
/** Теперь некоторые элементы может потребоваться переместить на новое место.
|
||||
* Элемент может остаться на месте, или переместиться в новое место "справа",
|
||||
* или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа".
|
||||
*/
|
||||
for (size_t i = 0; i < old_size; ++i)
|
||||
if (!ZeroTraits::check(buf[i].first))
|
||||
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 != buf[place_value])
|
||||
{
|
||||
++place_value;
|
||||
place_value &= mask();
|
||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
++collisions;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Если элемент остался на своём месте в старой цепочке разрешения коллизий.
|
||||
if (x == buf[place_value])
|
||||
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<Value *>(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()); }
|
||||
|
||||
|
||||
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;
|
||||
|
||||
// std::cerr << "m_size: " << m_size << ", " << "max_fill(): " << max_fill() << std::endl;
|
||||
// std::cerr << "m_size: " << m_size << std::endl;
|
||||
// std::cerr << "max_fill: " << max_fill() << std::endl;
|
||||
// std::cerr << "m_size > (1 << (size_degree - 1)): " << (m_size > (1 << (size_degree - 1))) << std::endl;
|
||||
// std::cerr << "!! buf: " << buf << std::endl;
|
||||
|
||||
if (unlikely(m_size > max_fill()))
|
||||
{
|
||||
// std::cerr << "resize" << std::endl;
|
||||
resize();
|
||||
return (*this)[x];
|
||||
}
|
||||
|
||||
// std::cerr << "size_degree: " << (int)size_degree << std::endl;
|
||||
// std::cerr << "!! buf: " << buf << std::endl;
|
||||
|
||||
return buf[place_value].second;
|
||||
}
|
||||
|
||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||
size_t getCollisions() const
|
||||
{
|
||||
return collisions;
|
||||
}
|
||||
#endif
|
||||
|
||||
void dump() const
|
||||
{
|
||||
// std::cerr << "buf: " << buf << std::endl;
|
||||
for (size_t i = 0; i < buf_size(); ++i)
|
||||
for (size_t i = 0; i < this->grower.bufSize(); ++i)
|
||||
{
|
||||
if (ZeroTraits::check(buf[i].first))
|
||||
if (this->buf[i].isZero(*this))
|
||||
std::cerr << "[ ]";
|
||||
else
|
||||
std::cerr << '[' << buf[i].first.data << ", " << buf[i].second << ']';
|
||||
std::cerr << '[' << this->buf[i].getValue().first.data << ", " << this->buf[i].getValue().second << ']';
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class HashTableAllocator
|
||||
{
|
||||
public:
|
||||
/// Выделить кусок памяти и заполнить его нулями.
|
||||
void * alloc(size_t size)
|
||||
{
|
||||
void * buf = ::calloc(size, 1);
|
||||
if (nullptr == buf)
|
||||
throwFromErrno("HashTableAllocator: Cannot calloc.", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/// Освободить память.
|
||||
void free(void * buf, size_t size)
|
||||
{
|
||||
::free(buf);
|
||||
}
|
||||
|
||||
/** Увеличить размер куска памяти.
|
||||
* Содержимое старого куска памяти переезжает в начало нового.
|
||||
* Оставшаяся часть заполняется нулями.
|
||||
* Положение куска памяти может измениться.
|
||||
*/
|
||||
void * realloc(void * buf, size_t old_size, size_t new_size)
|
||||
{
|
||||
buf = ::realloc(buf, new_size);
|
||||
if (nullptr == buf)
|
||||
throwFromErrno("HashTableAllocator: Cannot realloc.", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <size_t N>
|
||||
class HashTableAllocatorWithStackMemory : private HashTableAllocator
|
||||
{
|
||||
private:
|
||||
char stack_memory[N]{};
|
||||
|
||||
public:
|
||||
void * alloc(size_t size)
|
||||
{
|
||||
// std::cerr << "alloc(): size: " << size << std::endl;
|
||||
if (size <= N)
|
||||
return &stack_memory[0];
|
||||
|
||||
return HashTableAllocator::alloc(size);
|
||||
}
|
||||
|
||||
void free(void * buf, size_t size)
|
||||
{
|
||||
if (size > N)
|
||||
HashTableAllocator::free(buf, size);
|
||||
}
|
||||
|
||||
void * realloc(void * buf, size_t old_size, size_t new_size)
|
||||
{
|
||||
// std::cerr << "old_size: " << old_size << ", new_size: " << new_size << std::endl;
|
||||
|
||||
if (new_size <= N)
|
||||
return buf;
|
||||
|
||||
if (old_size > N)
|
||||
return HashTableAllocator::realloc(buf, old_size, new_size);
|
||||
|
||||
buf = ::malloc(new_size);
|
||||
if (nullptr == buf)
|
||||
throwFromErrno("HashTableAllocator: Cannot malloc.", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
memcpy(buf, stack_memory, old_size);
|
||||
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
||||
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct TrivialHash
|
||||
{
|
||||
size_t operator() (UInt64 x) const { return x; }
|
||||
size_t operator() (DB::StringRef x) const { return DB::parse<UInt64>(x.data); }
|
||||
size_t operator() (StringRef x) const { return DB::parse<UInt64>(x.data); }
|
||||
};
|
||||
|
||||
struct GrowthTraits : public DB::default_growth_traits
|
||||
struct Grower : public HashTableGrower
|
||||
{
|
||||
static const int INITIAL_SIZE_DEGREE = 2;
|
||||
static const int FAST_GROWTH_DEGREE = 1;
|
||||
static const size_t initial_size_degree = 2;
|
||||
Grower() { size_degree = initial_size_degree; }
|
||||
void increaseSize() { ++size_degree; }
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
typedef DB::HashMap<
|
||||
DB::StringRef,
|
||||
typedef HashMapWithDump<
|
||||
StringRef,
|
||||
UInt64,
|
||||
TrivialHash,
|
||||
DB::StringRefZeroTraits,
|
||||
GrowthTraits,
|
||||
DB::HashTableAllocatorWithStackMemory<4 * 24> > Map;
|
||||
Grower,
|
||||
HashTableAllocatorWithStackMemory<4 * 24> > Map;
|
||||
|
||||
Map map;
|
||||
|
||||
map.dump();
|
||||
std::cerr << "size: " << map.size() << std::endl;
|
||||
map[DB::StringRef("1", 1)] = 1;
|
||||
map[StringRef("1", 1)] = 1;
|
||||
map.dump();
|
||||
std::cerr << "size: " << map.size() << std::endl;
|
||||
map[DB::StringRef("9", 1)] = 1;
|
||||
map[StringRef("9", 1)] = 1;
|
||||
map.dump();
|
||||
std::cerr << "size: " << map.size() << std::endl;
|
||||
std::cerr << "Collisions: " << map.getCollisions() << std::endl;
|
||||
map[DB::StringRef("3", 1)] = 2;
|
||||
map[StringRef("3", 1)] = 2;
|
||||
map.dump();
|
||||
std::cerr << "size: " << map.size() << std::endl;
|
||||
std::cerr << "Collisions: " << map.getCollisions() << std::endl;
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include <unordered_set>
|
||||
#include <sparsehash/dense_hash_map>
|
||||
#include <sparsehash/dense_hash_set>
|
||||
#include <DB/Storages/ITableDeclaration.h>
|
||||
@ -10,7 +11,7 @@
|
||||
namespace DB
|
||||
{
|
||||
|
||||
bool ITableDeclaration::hasRealColumn(const String &column_name) const
|
||||
bool ITableDeclaration::hasRealColumn(const String & column_name) const
|
||||
{
|
||||
const NamesAndTypesList & real_columns = getColumnsList();
|
||||
for (auto & it : real_columns)
|
||||
@ -30,7 +31,7 @@ Names ITableDeclaration::getColumnNamesList() const
|
||||
}
|
||||
|
||||
|
||||
NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) const
|
||||
NameAndTypePair ITableDeclaration::getRealColumn(const String & column_name) const
|
||||
{
|
||||
const NamesAndTypesList & real_columns = getColumnsList();
|
||||
for (auto & it : real_columns)
|
||||
@ -40,13 +41,13 @@ NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) cons
|
||||
}
|
||||
|
||||
|
||||
bool ITableDeclaration::hasColumn(const String &column_name) const
|
||||
bool ITableDeclaration::hasColumn(const String & column_name) const
|
||||
{
|
||||
return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
|
||||
}
|
||||
|
||||
|
||||
NameAndTypePair ITableDeclaration::getColumn(const String &column_name) const
|
||||
NameAndTypePair ITableDeclaration::getColumn(const String & column_name) const
|
||||
{
|
||||
return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
6
|
@ -0,0 +1,2 @@
|
||||
SELECT max(arrayJoin(arr)) FROM (SELECT arrayEnumerateUniq(groupArray(intDiv(number, 54321)) AS nums, groupArray(toString(intDiv(number, 98765)))) AS arr FROM (SELECT number FROM system.numbers LIMIT 1000000) GROUP BY intHash32(number) % 100000)
|
||||
|
Loading…
Reference in New Issue
Block a user