2016-06-07 08:23:15 +00:00
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
|
|
#include <sparsehash/dense_hash_map>
|
|
|
|
|
#include <sparsehash/sparse_hash_map>
|
|
|
|
|
|
2017-04-01 09:19:00 +00:00
|
|
|
|
#include <Common/Stopwatch.h>
|
2016-06-07 08:23:15 +00:00
|
|
|
|
/*
|
|
|
|
|
#define DBMS_HASH_MAP_COUNT_COLLISIONS
|
|
|
|
|
*/
|
2017-04-01 09:19:00 +00:00
|
|
|
|
#include <Core/Types.h>
|
|
|
|
|
#include <IO/ReadBufferFromFile.h>
|
|
|
|
|
#include <IO/CompressedReadBuffer.h>
|
|
|
|
|
#include <Common/HashTable/HashMap.h>
|
|
|
|
|
#include <AggregateFunctions/IAggregateFunction.h>
|
|
|
|
|
#include <AggregateFunctions/AggregateFunctionFactory.h>
|
|
|
|
|
#include <DataTypes/DataTypesNumber.h>
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Тест проверяет скорость работы хэш-таблиц, имитируя их использование для агрегации.
|
|
|
|
|
* Первым аргументом указывается количество элементов, которое будет вставлено.
|
|
|
|
|
* Вторым аргументом может быть указано число от 1 до 4 - номер тестируемой структуры данных.
|
|
|
|
|
* Это важно, так как если запускать все тесты один за другим, то результаты будут некорректными.
|
|
|
|
|
* (Из-за особенностей работы аллокатора, первый тест получает преимущество.)
|
|
|
|
|
*
|
|
|
|
|
* В зависимости от USE_AUTO_ARRAY, выбирается одна из структур в качестве значения.
|
|
|
|
|
* USE_AUTO_ARRAY = 0 - используется std::vector (сложно-копируемая структура, sizeof = 24 байта).
|
2016-06-08 13:08:20 +00:00
|
|
|
|
* USE_AUTO_ARRAY = 1 - используется AutoArray (структура специально разработанная для таких случаев, sizeof = 8 байт).
|
2016-06-07 08:23:15 +00:00
|
|
|
|
*
|
2016-06-08 13:08:20 +00:00
|
|
|
|
* То есть, тест также позволяет сравнить AutoArray и std::vector.
|
2016-06-07 08:23:15 +00:00
|
|
|
|
*
|
|
|
|
|
* Если USE_AUTO_ARRAY = 0, то HashMap уверенно обгоняет всех.
|
|
|
|
|
* Если USE_AUTO_ARRAY = 1, то HashMap чуть менее серьёзно (20%) обгоняет google::dense_hash_map.
|
|
|
|
|
*
|
|
|
|
|
* При использовании HashMap, AutoArray имеет довольно серьёзное (40%) преимущество перед std::vector.
|
|
|
|
|
* А при использовании других хэш-таблиц, AutoArray ещё более серьёзно обгоняет std::vector
|
|
|
|
|
* (до трёх c половиной раз в случае std::unordered_map и google::sparse_hash_map).
|
|
|
|
|
*
|
|
|
|
|
* HashMap, в отличие от google::dense_hash_map, гораздо больше зависит от качества хэш-функции.
|
|
|
|
|
*
|
|
|
|
|
* PS. Измеряйте всё сами, а то я почти запутался.
|
|
|
|
|
*
|
|
|
|
|
* PPS. Сейчас при агрегации не используется массив агрегатных функций в качестве значений.
|
|
|
|
|
* Состояния агрегатных функций были отделены от интерфейса для манипуляции с ними, и кладутся в пул.
|
|
|
|
|
* Но в этом тесте осталось нечто похожее на старый сценарий использования хэш-таблиц при агрегации.
|
|
|
|
|
*/
|
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
#define USE_AUTO_ARRAY 0
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct AlternativeHash
|
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
|
size_t operator() (UInt64 x) const
|
|
|
|
|
{
|
|
|
|
|
x ^= x >> 23;
|
|
|
|
|
x *= 0x2127599bf4325c37ULL;
|
|
|
|
|
x ^= x >> 47;
|
|
|
|
|
|
|
|
|
|
return x;
|
|
|
|
|
}
|
2016-06-07 08:23:15 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(__x86_64__)
|
|
|
|
|
|
|
|
|
|
struct CRC32Hash_
|
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
|
size_t operator() (UInt64 x) const
|
|
|
|
|
{
|
|
|
|
|
UInt64 crc = -1ULL;
|
|
|
|
|
asm("crc32q %[x], %[crc]\n" : [crc] "+r" (crc) : [x] "rm" (x));
|
|
|
|
|
return crc;
|
|
|
|
|
}
|
2016-06-07 08:23:15 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int main(int argc, char ** argv)
|
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
|
using namespace DB;
|
2016-06-08 13:08:20 +00:00
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
using Key = UInt64;
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
|
|
|
|
#if USE_AUTO_ARRAY
|
2017-04-01 07:20:54 +00:00
|
|
|
|
using Value = AutoArray<IAggregateFunction*>;
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#else
|
2017-04-01 07:20:54 +00:00
|
|
|
|
using Value = std::vector<IAggregateFunction*>;
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
size_t n = argc < 2 ? 10000000 : atoi(argv[1]);
|
|
|
|
|
//size_t m = atoi(argv[2]);
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
AggregateFunctionFactory factory;
|
|
|
|
|
DataTypes data_types_empty;
|
|
|
|
|
DataTypes data_types_uint64;
|
|
|
|
|
data_types_uint64.push_back(std::make_shared<DataTypeUInt64>());
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
std::vector<Key> data(n);
|
|
|
|
|
Value value;
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
AggregateFunctionPtr func_count = factory.get("count", data_types_empty);
|
|
|
|
|
AggregateFunctionPtr func_avg = factory.get("avg", data_types_uint64);
|
|
|
|
|
AggregateFunctionPtr func_uniq = factory.get("uniq", data_types_uint64);
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
#define INIT \
|
|
|
|
|
{ \
|
|
|
|
|
value.resize(3); \
|
|
|
|
|
\
|
|
|
|
|
value[0] = func_count.get();\
|
|
|
|
|
value[1] = func_avg.get(); \
|
|
|
|
|
value[2] = func_uniq.get(); \
|
|
|
|
|
}
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
INIT;
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
|
|
|
|
#ifndef USE_AUTO_ARRAY
|
2017-04-01 07:20:54 +00:00
|
|
|
|
#undef INIT
|
|
|
|
|
#define INIT
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
Row row(1);
|
|
|
|
|
row[0] = UInt64(0);
|
|
|
|
|
|
|
|
|
|
std::cerr << "sizeof(Key) = " << sizeof(Key) << ", sizeof(Value) = " << sizeof(Value) << std::endl;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
Stopwatch watch;
|
|
|
|
|
/* for (size_t i = 0; i < n; ++i)
|
|
|
|
|
data[i] = rand() % m;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i += 10)
|
|
|
|
|
data[i] = 0;*/
|
|
|
|
|
|
|
|
|
|
ReadBufferFromFile in1("UniqID.bin");
|
|
|
|
|
CompressedReadBuffer in2(in1);
|
|
|
|
|
|
|
|
|
|
in2.readStrict(reinterpret_cast<char*>(&data[0]), sizeof(data[0]) * n);
|
|
|
|
|
|
|
|
|
|
watch.stop();
|
|
|
|
|
std::cerr << std::fixed << std::setprecision(2)
|
|
|
|
|
<< "Vector. Size: " << n
|
|
|
|
|
<< ", elapsed: " << watch.elapsedSeconds()
|
|
|
|
|
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
|
|
|
|
<< std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argc < 3 || atoi(argv[2]) == 1)
|
|
|
|
|
{
|
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
|
|
|
|
HashMap<Key, Value> map;
|
|
|
|
|
HashMap<Key, Value>::iterator it;
|
|
|
|
|
bool inserted;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; ++i)
|
|
|
|
|
{
|
|
|
|
|
map.emplace(data[i], it, inserted);
|
|
|
|
|
if (inserted)
|
|
|
|
|
{
|
|
|
|
|
new(&it->second) Value(std::move(value));
|
|
|
|
|
INIT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch.stop();
|
|
|
|
|
std::cerr << std::fixed << std::setprecision(2)
|
|
|
|
|
<< "HashMap. Size: " << map.size()
|
|
|
|
|
<< ", elapsed: " << watch.elapsedSeconds()
|
|
|
|
|
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
2017-04-01 07:20:54 +00:00
|
|
|
|
<< ", collisions: " << map.getCollisions()
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#endif
|
2017-04-01 07:20:54 +00:00
|
|
|
|
<< std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argc < 3 || atoi(argv[2]) == 2)
|
|
|
|
|
{
|
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
|
|
|
|
using Map = HashMap<Key, Value, AlternativeHash>;
|
|
|
|
|
Map map;
|
|
|
|
|
Map::iterator it;
|
|
|
|
|
bool inserted;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; ++i)
|
|
|
|
|
{
|
|
|
|
|
map.emplace(data[i], it, inserted);
|
|
|
|
|
if (inserted)
|
|
|
|
|
{
|
|
|
|
|
new(&it->second) Value(std::move(value));
|
|
|
|
|
INIT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch.stop();
|
|
|
|
|
std::cerr << std::fixed << std::setprecision(2)
|
|
|
|
|
<< "HashMap, AlternativeHash. Size: " << map.size()
|
|
|
|
|
<< ", elapsed: " << watch.elapsedSeconds()
|
|
|
|
|
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
2017-04-01 07:20:54 +00:00
|
|
|
|
<< ", collisions: " << map.getCollisions()
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#endif
|
2017-04-01 07:20:54 +00:00
|
|
|
|
<< std::endl;
|
|
|
|
|
}
|
2016-06-07 08:23:15 +00:00
|
|
|
|
|
|
|
|
|
#if defined(__x86_64__)
|
2017-04-01 07:20:54 +00:00
|
|
|
|
if (argc < 3 || atoi(argv[2]) == 3)
|
|
|
|
|
{
|
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
|
|
|
|
using Map = HashMap<Key, Value, CRC32Hash_>;
|
|
|
|
|
Map map;
|
|
|
|
|
Map::iterator it;
|
|
|
|
|
bool inserted;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; ++i)
|
|
|
|
|
{
|
|
|
|
|
map.emplace(data[i], it, inserted);
|
|
|
|
|
if (inserted)
|
|
|
|
|
{
|
|
|
|
|
new(&it->second) Value(std::move(value));
|
|
|
|
|
INIT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch.stop();
|
|
|
|
|
std::cerr << std::fixed << std::setprecision(2)
|
|
|
|
|
<< "HashMap, CRC32Hash. Size: " << map.size()
|
|
|
|
|
<< ", elapsed: " << watch.elapsedSeconds()
|
|
|
|
|
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
2017-04-01 07:20:54 +00:00
|
|
|
|
<< ", collisions: " << map.getCollisions()
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#endif
|
2017-04-01 07:20:54 +00:00
|
|
|
|
<< std::endl;
|
|
|
|
|
}
|
2016-06-07 08:23:15 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
if (argc < 3 || atoi(argv[2]) == 4)
|
|
|
|
|
{
|
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
INIT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch.stop();
|
|
|
|
|
std::cerr << std::fixed << std::setprecision(2)
|
|
|
|
|
<< "std::unordered_map. Size: " << map.size()
|
|
|
|
|
<< ", elapsed: " << watch.elapsedSeconds()
|
|
|
|
|
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
|
|
|
|
<< std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argc < 3 || atoi(argv[2]) == 5)
|
|
|
|
|
{
|
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
it = map.insert(std::make_pair(data[i], std::move(value))).first;
|
|
|
|
|
INIT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch.stop();
|
|
|
|
|
std::cerr << std::fixed << std::setprecision(2)
|
|
|
|
|
<< "google::dense_hash_map. Size: " << map.size()
|
|
|
|
|
<< ", elapsed: " << watch.elapsedSeconds()
|
|
|
|
|
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
|
|
|
|
<< std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argc < 3 || atoi(argv[2]) == 6)
|
|
|
|
|
{
|
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
|
|
|
|
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)));
|
|
|
|
|
INIT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch.stop();
|
|
|
|
|
std::cerr << std::fixed << std::setprecision(2)
|
|
|
|
|
<< "google::sparse_hash_map. Size: " << map.size()
|
|
|
|
|
<< ", elapsed: " << watch.elapsedSeconds()
|
|
|
|
|
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
|
|
|
|
|
<< std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
2016-06-07 08:23:15 +00:00
|
|
|
|
}
|