2015-02-22 07:23:37 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <statdaemons/HyperLogLogCounter.h>
|
|
|
|
|
#include <DB/Common/HashTable/SmallTable.h>
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Для маленького количества ключей - массив фиксированного размера "на стеке".
|
|
|
|
|
* Для большого - выделяется HyperLogLog.
|
|
|
|
|
* NOTE Возможно, имеет смысл сделать реализацию для среднего размера в виде хэш-таблицы.
|
|
|
|
|
*/
|
|
|
|
|
template <
|
|
|
|
|
typename Key,
|
|
|
|
|
UInt8 small_set_size,
|
|
|
|
|
UInt8 K,
|
|
|
|
|
typename Hash = IntHash32<Key>,
|
2015-08-11 17:31:31 +00:00
|
|
|
|
typename DenominatorType = double>
|
2015-02-22 07:23:37 +00:00
|
|
|
|
class HyperLogLogWithSmallSetOptimization
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
using Small = SmallSet<Key, small_set_size>;
|
2015-08-20 17:11:21 +00:00
|
|
|
|
using Large = HyperLogLogCounter<K, Hash, UInt32, DenominatorType>;
|
2015-02-22 07:23:37 +00:00
|
|
|
|
|
|
|
|
|
Small small;
|
|
|
|
|
Large * large = nullptr;
|
|
|
|
|
|
|
|
|
|
bool isLarge() const
|
|
|
|
|
{
|
|
|
|
|
return large != nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void toLarge()
|
|
|
|
|
{
|
|
|
|
|
if (current_memory_tracker)
|
|
|
|
|
current_memory_tracker->alloc(sizeof(large));
|
|
|
|
|
|
|
|
|
|
/// На время копирования данных из tiny, устанавливать значение large ещё нельзя (иначе оно перезатрёт часть данных).
|
|
|
|
|
Large * tmp_large = new Large;
|
|
|
|
|
|
|
|
|
|
for (const auto & x : small)
|
|
|
|
|
tmp_large->insert(x);
|
|
|
|
|
|
|
|
|
|
large = tmp_large;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
~HyperLogLogWithSmallSetOptimization()
|
|
|
|
|
{
|
|
|
|
|
if (isLarge())
|
|
|
|
|
{
|
|
|
|
|
delete large;
|
|
|
|
|
|
|
|
|
|
if (current_memory_tracker)
|
|
|
|
|
current_memory_tracker->free(sizeof(large));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void insert(Key value)
|
|
|
|
|
{
|
|
|
|
|
if (!isLarge())
|
|
|
|
|
{
|
|
|
|
|
if (small.find(value) == small.end())
|
|
|
|
|
{
|
|
|
|
|
if (!small.full())
|
|
|
|
|
small.insert(value);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
toLarge();
|
|
|
|
|
large->insert(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
large->insert(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UInt32 size() const
|
|
|
|
|
{
|
|
|
|
|
return !isLarge() ? small.size() : large->size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void merge(const HyperLogLogWithSmallSetOptimization & rhs)
|
|
|
|
|
{
|
|
|
|
|
if (rhs.isLarge())
|
|
|
|
|
{
|
|
|
|
|
if (!isLarge())
|
|
|
|
|
toLarge();
|
|
|
|
|
|
|
|
|
|
large->merge(*rhs.large);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (const auto & x : rhs.small)
|
|
|
|
|
insert(x);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Можно вызывать только для пустого объекта.
|
|
|
|
|
void read(DB::ReadBuffer & in)
|
|
|
|
|
{
|
|
|
|
|
bool is_large;
|
|
|
|
|
readBinary(is_large, in);
|
|
|
|
|
|
|
|
|
|
if (is_large)
|
|
|
|
|
{
|
|
|
|
|
toLarge();
|
|
|
|
|
large->read(in);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
small.read(in);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void readAndMerge(DB::ReadBuffer & in)
|
|
|
|
|
{
|
2015-07-24 16:46:00 +00:00
|
|
|
|
bool is_rhs_large;
|
|
|
|
|
readBinary(is_rhs_large, in);
|
|
|
|
|
|
|
|
|
|
if (!isLarge() && is_rhs_large)
|
|
|
|
|
toLarge();
|
|
|
|
|
|
|
|
|
|
if (!is_rhs_large)
|
|
|
|
|
{
|
|
|
|
|
typename Small::Reader reader(in);
|
|
|
|
|
while (reader.next())
|
|
|
|
|
insert(reader.get());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
large->readAndMerge(in);
|
2015-02-22 07:23:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void write(DB::WriteBuffer & out) const
|
|
|
|
|
{
|
|
|
|
|
writeBinary(isLarge(), out);
|
|
|
|
|
|
|
|
|
|
if (isLarge())
|
|
|
|
|
large->write(out);
|
|
|
|
|
else
|
|
|
|
|
small.write(out);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|