dbms: added SmallSet and SmallMap [#METR-2944].

This commit is contained in:
Alexey Milovidov 2015-02-21 03:28:53 +03:00
parent f7ef574ed2
commit 5070e7642d
2 changed files with 401 additions and 0 deletions

View File

@ -0,0 +1,328 @@
#pragma once
#include <DB/Common/HashTable/HashMap.h>
/** Замена хэш-таблицы для маленького количества (единицы) ключей.
* Реализована в виде массива с линейным поиском.
* Массив расположен внутри объекта.
* Интерфейс является подмножеством интерфейса HashTable.
*
* Вставка возможна только если метод full возвращает false.
* При неизвестном количестве различных ключей,
* вы должны проверять, не заполнена ли таблица,
* и делать fallback в этом случае (например, использовать полноценную хэш-таблицу).
*/
template
<
typename Key,
typename Cell,
size_t capacity
>
class SmallTable :
private boost::noncopyable,
protected Cell::State
{
protected:
friend class const_iterator;
friend class iterator;
typedef SmallTable<Key, Cell, capacity> Self;
typedef Cell cell_type;
size_t m_size = 0; /// Количество элементов.
Cell buf[capacity]; /// Кусок памяти для всех элементов.
/// Найти ячейку с тем же ключём или пустую ячейку, начиная с заданного места и далее по цепочке разрешения коллизий.
const Cell * ALWAYS_INLINE findCell(const Key & x) const
{
const Cell * it = buf;
while (it < buf + m_size)
{
if (it->keyEquals(x))
break;
++it;
}
return it;
}
Cell * ALWAYS_INLINE findCell(const Key & x)
{
Cell * it = buf;
while (it < buf + m_size)
{
if (it->keyEquals(x))
break;
++it;
}
return it;
}
public:
typedef Key key_type;
typedef typename Cell::value_type value_type;
class iterator
{
Self * container;
Cell * ptr;
friend class SmallTable;
public:
iterator() {}
iterator(Self * container_, Cell * ptr_) : container(container_), ptr(ptr_) {}
bool operator== (const iterator & rhs) const { return ptr == rhs.ptr; }
bool operator!= (const iterator & rhs) const { return ptr != rhs.ptr; }
iterator & operator++()
{
++ptr;
return *this;
}
value_type & operator* () const { return ptr->getValue(); }
value_type * operator->() const { return &ptr->getValue(); }
Cell * getPtr() const { return ptr; }
};
class const_iterator
{
const Self * container;
const Cell * ptr;
friend class SmallTable;
public:
const_iterator() {}
const_iterator(const Self * container_, const Cell * ptr_) : container(container_), ptr(ptr_) {}
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++()
{
++ptr;
return *this;
}
const value_type & operator* () const { return ptr->getValue(); }
const value_type * operator->() const { return &ptr->getValue(); }
const Cell * getPtr() const { return ptr; }
};
const_iterator begin() const { return iteratorTo(buf); }
iterator begin() { return iteratorTo(buf); }
const_iterator end() const { return iteratorTo(buf + m_size); }
iterator end() { return iteratorTo(buf + m_size); }
protected:
const_iterator iteratorTo(const Cell * ptr) const { return const_iterator(this, ptr); }
iterator iteratorTo(Cell * ptr) { return iterator(this, ptr); }
public:
/** Таблица переполнена.
* В переполненную таблицу ничего нельзя вставлять.
*/
bool full()
{
return m_size == capacity;
}
/// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace.
std::pair<iterator, bool> ALWAYS_INLINE insert(const value_type & x)
{
std::pair<iterator, bool> res;
emplace(Cell::getKey(x), res.first, res.second);
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 ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted)
{
Cell * res = findCell(x);
it = iteratorTo(res);
inserted = res == buf + m_size;
if (inserted)
{
new(res) Cell(x, *this);
++m_size;
}
}
/// Скопировать ячейку из другой хэш-таблицы. Предполагается, что такого ключа в таблице ещё не было.
void ALWAYS_INLINE insertUnique(const Cell * cell)
{
memcpy(&buf[m_size], cell, sizeof(*cell));
++m_size;
}
iterator ALWAYS_INLINE find(Key x) { return iteratorTo(findCell(x)); }
const_iterator ALWAYS_INLINE find(Key x) const { return iteratorTo(findCell(x)); }
void write(DB::WriteBuffer & wb) const
{
Cell::State::write(wb);
DB::writeVarUInt(m_size, wb);
for (size_t i = 0; i < m_size; ++i)
buf[i].write(wb);
}
void writeText(DB::WriteBuffer & wb) const
{
Cell::State::writeText(wb);
DB::writeText(m_size, wb);
for (size_t i = 0; i < m_size; ++i)
{
DB::writeChar(',', wb);
buf[i].writeText(wb);
}
}
void read(DB::ReadBuffer & rb)
{
Cell::State::read(rb);
m_size = 0;
size_t new_size = 0;
DB::readVarUInt(new_size, rb);
if (new_size > capacity)
throw DB::Exception("Illegal size");
for (size_t i = 0; i < new_size; ++i)
buf[i].read(rb);
m_size = new_size;
}
void readText(DB::ReadBuffer & rb)
{
Cell::State::readText(rb);
m_size = 0;
size_t new_size = 0;
DB::readText(new_size, rb);
if (new_size > capacity)
throw DB::Exception("Illegal size");
for (size_t i = 0; i < new_size; ++i)
{
DB::assertString(",", rb);
buf[i].readText(rb);
}
m_size = new_size;
}
size_t size() const
{
return m_size;
}
bool empty() const
{
return 0 == m_size;
}
void clear()
{
if (!__has_trivial_destructor(Cell))
for (iterator it = begin(); it != end(); ++it)
it.ptr->~Cell();
m_size = 0;
}
size_t getBufferSizeInBytes() const
{
return sizeof(buf);
}
};
struct HashUnused {};
template
<
typename Key,
size_t capacity
>
using SmallSet = SmallTable<Key, HashTableCell<Key, HashUnused>, capacity>;
template
<
typename Key,
typename Cell,
size_t capacity
>
class SmallMapTable : public SmallTable<Key, Cell, capacity>
{
public:
typedef Key key_type;
typedef typename Cell::Mapped mapped_type;
typedef typename Cell::value_type value_type;
mapped_type & ALWAYS_INLINE operator[](Key x)
{
typename SmallMapTable::iterator it;
bool inserted;
this->emplace(x, it, inserted);
new(&it->second) mapped_type();
return it->second;
}
};
template
<
typename Key,
typename Mapped,
size_t capacity
>
using SmallMap = SmallMapTable<Key, HashMapCell<Key, Mapped, HashUnused>, capacity>;

View File

@ -0,0 +1,73 @@
#include <iostream>
#include <iomanip>
#include <DB/Interpreters/AggregationCommon.h>
#include <DB/Common/HashTable/SmallTable.h>
int main(int argc, char ** argv)
{
{
typedef SmallSet<int, 16> 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 SmallMap<int, std::string, 16> Cont;
Cont cont;
cont.insert(Cont::value_type(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 SmallSet<DB::UInt128, 16> Cont;
Cont cont;
std::string dump;
{
DB::WriteBufferFromString wb(dump);
cont.write(wb);
}
std::cerr << "dump: " << dump << std::endl;
}
return 0;
}