// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019 // // 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" #include "td/utils/HazardPointers.h" #include "td/utils/port/thread.h" #include "td/utils/SpinLock.h" #include "td/utils/tests.h" #include #include #include #if !TD_THREAD_UNSUPPORTED #if TD_HAVE_ABSL #include #else #include #endif #if TD_WITH_LIBCUCKOO #include #endif #if TD_WITH_JUNCTION #include #include #include #endif namespace td { // Non resizable HashMap. Just an example template class ArrayHashMap { public: explicit ArrayHashMap(size_t n) : array_(n) { } struct Node { std::atomic key{KeyT{}}; std::atomic value{ValueT{}}; }; 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> array_; }; template class ConcurrentHashMapMutex { public: explicit ConcurrentHashMapMutex(size_t) { } static std::string get_name() { return "ConcurrentHashMapMutex"; } void insert(KeyT key, ValueT value) { std::unique_lock lock(mutex_); hash_map_.emplace(key, value); } ValueT find(KeyT key, ValueT default_value) { std::unique_lock lock(mutex_); auto it = hash_map_.find(key); if (it == hash_map_.end()) { return default_value; } return it->second; } private: std::mutex mutex_; #if TD_HAVE_ABSL absl::flat_hash_map hash_map_; #else std::unordered_map hash_map_; #endif }; template class ConcurrentHashMapSpinlock { public: explicit ConcurrentHashMapSpinlock(size_t) { } 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: SpinLock spinlock_; #if TD_HAVE_ABSL absl::flat_hash_map hash_map_; #else std::unordered_map hash_map_; #endif }; #if TD_WITH_LIBCUCKOO template class ConcurrentHashMapLibcuckoo { public: explicit ConcurrentHashMapLibcuckoo(size_t) { } 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 hash_map_; }; #endif #if TD_WITH_JUNCTION template class ConcurrentHashMapJunction { public: explicit ConcurrentHashMapJunction(size_t size) : hash_map_() { } 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); } ConcurrentHashMapJunction(const ConcurrentHashMapJunction &) = delete; ConcurrentHashMapJunction &operator=(const ConcurrentHashMapJunction &) = delete; ConcurrentHashMapJunction(ConcurrentHashMapJunction &&other) = delete; ConcurrentHashMapJunction &operator=(ConcurrentHashMapJunction &&) = delete; ~ConcurrentHashMapJunction() { junction::DefaultQSBR.flush(); } private: junction::ConcurrentMap_Leapfrog hash_map_; }; #endif } // namespace td template class HashMapBenchmark : public td::Benchmark { struct Query { int key; int value; }; std::vector queries; td::unique_ptr hash_map; size_t threads_n = 16; int mod_; static constexpr size_t mul_ = 7273; //1000000000 + 7; int n_; public: explicit HashMapBenchmark(size_t threads_n) : threads_n(threads_n) { } std::string get_description() const override { return hash_map->get_name(); } void start_up_n(int n) override { n *= (int)threads_n; n_ = n; hash_map = td::make_unique(n * 2); } void run(int n) override { n = n_; std::vector 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++) { auto x = int((i + 1) * mul_ % n_) + 3; auto y = int(i + 2); hash_map->insert(x, y); } }); } for (auto &thread : threads) { thread.join(); } } void tear_down() override { for (int i = 0; i < n_; i++) { auto x = int((i + 1) * mul_ % n_) + 3; auto y = int(i + 2); ASSERT_EQ(y, hash_map->find(x, -1)); } queries.clear(); hash_map.reset(); } }; template static void bench_hash_map() { td::bench(HashMapBenchmark(16)); td::bench(HashMapBenchmark(1)); } TEST(ConcurrentHashMap, Benchmark) { bench_hash_map>(); bench_hash_map>(); bench_hash_map>(); bench_hash_map>(); #if TD_WITH_LIBCUCKOO bench_hash_map>(); #endif #if TD_WITH_JUNCTION bench_hash_map>(); #endif } #endif