2019-07-07 02:05:30 +02:00
|
|
|
//
|
2022-01-01 01:35:39 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
|
2019-07-07 02:05:30 +02:00
|
|
|
//
|
|
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
//
|
|
|
|
#include "td/utils/benchmark.h"
|
|
|
|
#include "td/utils/ConcurrentHashTable.h"
|
2019-12-24 14:46:10 +01:00
|
|
|
#include "td/utils/misc.h"
|
2022-07-14 14:27:06 +02:00
|
|
|
#include "td/utils/port/Mutex.h"
|
2019-07-21 20:07:07 +02:00
|
|
|
#include "td/utils/port/thread.h"
|
|
|
|
#include "td/utils/SpinLock.h"
|
|
|
|
#include "td/utils/tests.h"
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
|
|
|
|
#if !TD_THREAD_UNSUPPORTED
|
2019-07-07 02:05:30 +02:00
|
|
|
|
|
|
|
#if TD_HAVE_ABSL
|
|
|
|
#include <absl/container/flat_hash_map.h>
|
|
|
|
#else
|
|
|
|
#include <unordered_map>
|
|
|
|
#endif
|
|
|
|
|
2019-07-21 20:07:07 +02:00
|
|
|
#if TD_WITH_LIBCUCKOO
|
2019-07-07 02:05:30 +02:00
|
|
|
#include <third-party/libcuckoo/libcuckoo/cuckoohash_map.hh>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if TD_WITH_JUNCTION
|
|
|
|
#include <junction/ConcurrentMap_Grampa.h>
|
|
|
|
#include <junction/ConcurrentMap_Leapfrog.h>
|
2019-07-23 00:50:12 +02:00
|
|
|
#include <junction/ConcurrentMap_Linear.h>
|
2019-07-07 02:05:30 +02:00
|
|
|
#endif
|
2019-07-21 20:07:07 +02:00
|
|
|
|
2019-07-07 02:05:30 +02:00
|
|
|
namespace td {
|
|
|
|
|
|
|
|
// Non resizable HashMap. Just an example
|
|
|
|
template <class KeyT, class ValueT>
|
|
|
|
class ArrayHashMap {
|
|
|
|
public:
|
2019-07-21 20:07:07 +02:00
|
|
|
explicit ArrayHashMap(size_t n) : array_(n) {
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
|
|
|
struct Node {
|
|
|
|
std::atomic<KeyT> key{KeyT{}};
|
2019-07-22 06:01:51 +02:00
|
|
|
std::atomic<ValueT> value{ValueT{}};
|
2019-07-07 02:05:30 +02:00
|
|
|
};
|
|
|
|
static std::string get_name() {
|
|
|
|
return "ArrayHashMap";
|
|
|
|
}
|
|
|
|
KeyT empty_key() const {
|
|
|
|
return KeyT{};
|
|
|
|
}
|
|
|
|
|
|
|
|
void insert(KeyT key, ValueT value) {
|
|
|
|
array_.with_value(key, true, [&](auto &node_value) { node_value.store(value, std::memory_order_release); });
|
|
|
|
}
|
|
|
|
ValueT find(KeyT key, ValueT value) {
|
|
|
|
array_.with_value(key, false, [&](auto &node_value) { value = node_value.load(std::memory_order_acquire); });
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
AtomicHashArray<KeyT, std::atomic<ValueT>> array_;
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class KeyT, class ValueT>
|
|
|
|
class ConcurrentHashMapMutex {
|
|
|
|
public:
|
2019-07-21 20:07:07 +02:00
|
|
|
explicit ConcurrentHashMapMutex(size_t) {
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
|
|
|
static std::string get_name() {
|
|
|
|
return "ConcurrentHashMapMutex";
|
|
|
|
}
|
|
|
|
void insert(KeyT key, ValueT value) {
|
2022-07-14 14:27:06 +02:00
|
|
|
auto guard = mutex_.lock();
|
2019-07-07 02:05:30 +02:00
|
|
|
hash_map_.emplace(key, value);
|
|
|
|
}
|
|
|
|
ValueT find(KeyT key, ValueT default_value) {
|
2022-07-14 14:27:06 +02:00
|
|
|
auto guard = mutex_.lock();
|
2019-07-07 02:05:30 +02:00
|
|
|
auto it = hash_map_.find(key);
|
|
|
|
if (it == hash_map_.end()) {
|
|
|
|
return default_value;
|
|
|
|
}
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2022-07-14 14:27:06 +02:00
|
|
|
Mutex mutex_;
|
2019-07-07 02:05:30 +02:00
|
|
|
#if TD_HAVE_ABSL
|
|
|
|
absl::flat_hash_map<KeyT, ValueT> hash_map_;
|
|
|
|
#else
|
|
|
|
std::unordered_map<KeyT, ValueT> hash_map_;
|
|
|
|
#endif
|
|
|
|
};
|
2019-07-21 20:07:07 +02:00
|
|
|
|
2019-07-07 02:05:30 +02:00
|
|
|
template <class KeyT, class ValueT>
|
|
|
|
class ConcurrentHashMapSpinlock {
|
|
|
|
public:
|
2019-07-21 20:07:07 +02:00
|
|
|
explicit ConcurrentHashMapSpinlock(size_t) {
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
|
|
|
static std::string get_name() {
|
|
|
|
return "ConcurrentHashMapSpinlock";
|
|
|
|
}
|
|
|
|
void insert(KeyT key, ValueT value) {
|
|
|
|
auto guard = spinlock_.lock();
|
|
|
|
hash_map_.emplace(key, value);
|
|
|
|
}
|
|
|
|
ValueT find(KeyT key, ValueT default_value) {
|
|
|
|
auto guard = spinlock_.lock();
|
|
|
|
auto it = hash_map_.find(key);
|
|
|
|
if (it == hash_map_.end()) {
|
|
|
|
return default_value;
|
|
|
|
}
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2019-07-21 20:07:07 +02:00
|
|
|
SpinLock spinlock_;
|
2019-07-07 02:05:30 +02:00
|
|
|
#if TD_HAVE_ABSL
|
|
|
|
absl::flat_hash_map<KeyT, ValueT> hash_map_;
|
|
|
|
#else
|
|
|
|
std::unordered_map<KeyT, ValueT> hash_map_;
|
|
|
|
#endif
|
|
|
|
};
|
2019-07-21 20:07:07 +02:00
|
|
|
|
2019-07-07 02:05:30 +02:00
|
|
|
#if TD_WITH_LIBCUCKOO
|
|
|
|
template <class KeyT, class ValueT>
|
|
|
|
class ConcurrentHashMapLibcuckoo {
|
|
|
|
public:
|
2019-07-21 20:07:07 +02:00
|
|
|
explicit ConcurrentHashMapLibcuckoo(size_t) {
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
|
|
|
static std::string get_name() {
|
|
|
|
return "ConcurrentHashMapLibcuckoo";
|
|
|
|
}
|
|
|
|
void insert(KeyT key, ValueT value) {
|
|
|
|
hash_map_.insert(key, value);
|
|
|
|
}
|
|
|
|
ValueT find(KeyT key, ValueT default_value) {
|
|
|
|
hash_map_.find(key, default_value);
|
|
|
|
return default_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
cuckoohash_map<KeyT, ValueT> hash_map_;
|
|
|
|
};
|
|
|
|
#endif
|
2019-07-21 20:07:07 +02:00
|
|
|
|
2019-07-07 02:05:30 +02:00
|
|
|
#if TD_WITH_JUNCTION
|
|
|
|
template <class KeyT, class ValueT>
|
|
|
|
class ConcurrentHashMapJunction {
|
|
|
|
public:
|
2019-07-21 20:07:07 +02:00
|
|
|
explicit ConcurrentHashMapJunction(size_t size) : hash_map_() {
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
|
|
|
static std::string get_name() {
|
|
|
|
return "ConcurrentHashMapJunction";
|
|
|
|
}
|
|
|
|
void insert(KeyT key, ValueT value) {
|
|
|
|
hash_map_.assign(key, value);
|
|
|
|
}
|
|
|
|
ValueT find(KeyT key, ValueT default_value) {
|
|
|
|
return hash_map_.get(key);
|
|
|
|
}
|
2019-07-21 20:07:07 +02:00
|
|
|
|
|
|
|
ConcurrentHashMapJunction(const ConcurrentHashMapJunction &) = delete;
|
|
|
|
ConcurrentHashMapJunction &operator=(const ConcurrentHashMapJunction &) = delete;
|
|
|
|
ConcurrentHashMapJunction(ConcurrentHashMapJunction &&other) = delete;
|
|
|
|
ConcurrentHashMapJunction &operator=(ConcurrentHashMapJunction &&) = delete;
|
2019-07-07 02:05:30 +02:00
|
|
|
~ConcurrentHashMapJunction() {
|
|
|
|
junction::DefaultQSBR.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
junction::ConcurrentMap_Leapfrog<KeyT, ValueT> hash_map_;
|
|
|
|
};
|
|
|
|
#endif
|
2019-07-21 20:07:07 +02:00
|
|
|
|
2019-07-07 02:05:30 +02:00
|
|
|
} // namespace td
|
|
|
|
|
|
|
|
template <class HashMap>
|
2021-07-04 04:58:54 +02:00
|
|
|
class HashMapBenchmark final : public td::Benchmark {
|
2019-07-07 02:05:30 +02:00
|
|
|
struct Query {
|
|
|
|
int key;
|
|
|
|
int value;
|
|
|
|
};
|
|
|
|
std::vector<Query> queries;
|
2019-07-21 20:07:07 +02:00
|
|
|
td::unique_ptr<HashMap> hash_map;
|
2019-07-07 02:05:30 +02:00
|
|
|
|
|
|
|
size_t threads_n = 16;
|
2020-06-16 04:10:16 +02:00
|
|
|
static constexpr size_t MUL = 7273; //1000000000 + 7;
|
2021-11-11 15:39:09 +01:00
|
|
|
int n_ = 0;
|
2019-07-07 02:05:30 +02:00
|
|
|
|
|
|
|
public:
|
2019-07-21 20:07:07 +02:00
|
|
|
explicit HashMapBenchmark(size_t threads_n) : threads_n(threads_n) {
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
2021-07-03 22:51:36 +02:00
|
|
|
std::string get_description() const final {
|
2019-09-09 01:49:13 +02:00
|
|
|
return HashMap::get_name();
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
2021-07-03 22:51:36 +02:00
|
|
|
void start_up_n(int n) final {
|
2020-06-13 04:13:11 +02:00
|
|
|
n *= static_cast<int>(threads_n);
|
2019-07-07 02:05:30 +02:00
|
|
|
n_ = n;
|
2019-07-21 20:07:07 +02:00
|
|
|
hash_map = td::make_unique<HashMap>(n * 2);
|
2019-07-07 02:05:30 +02:00
|
|
|
}
|
|
|
|
|
2021-07-03 22:51:36 +02:00
|
|
|
void run(int n) final {
|
2019-07-07 02:05:30 +02:00
|
|
|
n = n_;
|
|
|
|
std::vector<td::thread> threads;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < threads_n; i++) {
|
|
|
|
size_t l = n * i / threads_n;
|
|
|
|
size_t r = n * (i + 1) / threads_n;
|
|
|
|
threads.emplace_back([l, r, this] {
|
|
|
|
for (size_t i = l; i < r; i++) {
|
2020-06-16 04:10:16 +02:00
|
|
|
auto x = td::narrow_cast<int>((i + 1) * MUL % n_) + 3;
|
2019-12-24 14:46:10 +01:00
|
|
|
auto y = td::narrow_cast<int>(i + 2);
|
2019-07-07 02:05:30 +02:00
|
|
|
hash_map->insert(x, y);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
for (auto &thread : threads) {
|
|
|
|
thread.join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-03 22:51:36 +02:00
|
|
|
void tear_down() final {
|
2019-07-07 02:05:30 +02:00
|
|
|
for (int i = 0; i < n_; i++) {
|
2020-06-16 04:10:16 +02:00
|
|
|
auto x = td::narrow_cast<int>((i + 1) * MUL % n_) + 3;
|
2019-12-24 14:46:10 +01:00
|
|
|
auto y = td::narrow_cast<int>(i + 2);
|
2019-07-07 02:05:30 +02:00
|
|
|
ASSERT_EQ(y, hash_map->find(x, -1));
|
|
|
|
}
|
|
|
|
queries.clear();
|
|
|
|
hash_map.reset();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class HashMap>
|
2019-07-22 06:01:51 +02:00
|
|
|
static void bench_hash_map() {
|
2019-07-07 02:05:30 +02:00
|
|
|
td::bench(HashMapBenchmark<HashMap>(16));
|
|
|
|
td::bench(HashMapBenchmark<HashMap>(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ConcurrentHashMap, Benchmark) {
|
|
|
|
bench_hash_map<td::ConcurrentHashMap<int, int>>();
|
|
|
|
bench_hash_map<td::ArrayHashMap<int, int>>();
|
|
|
|
bench_hash_map<td::ConcurrentHashMapSpinlock<int, int>>();
|
|
|
|
bench_hash_map<td::ConcurrentHashMapMutex<int, int>>();
|
|
|
|
#if TD_WITH_LIBCUCKOO
|
|
|
|
bench_hash_map<td::ConcurrentHashMapLibcuckoo<int, int>>();
|
|
|
|
#endif
|
|
|
|
#if TD_WITH_JUNCTION
|
|
|
|
bench_hash_map<td::ConcurrentHashMapJunction<int, int>>();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-06-24 13:47:36 +02:00
|
|
|
#endif
|