Last active
September 7, 2021 18:18
-
-
Save commander-trashdin/d67d69c79ab8944d8f3442e49678b0a0 to your computer and use it in GitHub Desktop.
concurrent hash map
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <unordered_map> | |
| #include <mutex> | |
| #include <functional> | |
| #include <forward_list> | |
| #include <vector> | |
| #include <utility> | |
| #include <algorithm> | |
| #include <memory> | |
| template <class K, class V, class Hash = std::hash<K>> | |
| class ConcurrentHashMap { | |
| public: | |
| ConcurrentHashMap(const Hash& hasher = Hash()) : ConcurrentHashMap(kUndefinedSize, hasher) { | |
| } | |
| explicit ConcurrentHashMap(int expected_size, const Hash& hasher = Hash()) | |
| : ConcurrentHashMap(expected_size, kDefaultConcurrencyLevel, hasher) { | |
| } | |
| ConcurrentHashMap(int expected_size, int expected_threads_count, const Hash& hasher = Hash()) | |
| : hasher_(hasher), mutexes_(expected_threads_count) { | |
| table_.resize(expected_size); | |
| } | |
| bool Insert(const K& key, const V& value) { | |
| if (static_cast<float>(count) / table_.size() > 0.75) { | |
| Resize(std::max(static_cast<size_t>(2), 2 * count + 1)); | |
| } | |
| auto hash = hasher_(key) % table_.size(); | |
| auto group = hash / (table_.size() / mutexes_.size()); | |
| std::lock_guard<std::mutex> lock(*mutexes_.at(group)); | |
| bool key_esists = | |
| (std::find_if(table_[hash].begin(), table_[hash].end(), [&key](const auto& pair) { | |
| return pair.first == key; | |
| }) != std::end(table_[hash])); | |
| if (key_esists) { | |
| return false; | |
| } | |
| table_[hash].emplace_front(key, value); | |
| count++; | |
| return true; | |
| // probably bs | |
| } | |
| bool Erase(const K& key) { | |
| auto hash = hasher_(key) % table_.size(); | |
| auto group = hash / (table_.size() / mutexes_.size()); | |
| std::lock_guard<std::mutex> lock(*mutexes_.at(group)); | |
| bool key_esists = | |
| (std::find_if(table_[hash].begin(), table_[hash].end(), [&key](const auto& pair) { | |
| return pair.first == key; | |
| }) != std::end(table_[hash])); | |
| if (!key_esists) { | |
| return false; | |
| } | |
| table_[hash].remove_if([&key](const auto& pair) { return pair.first == key; }); | |
| count--; | |
| if (static_cast<float>(count) / table_.size() < 0.25) { | |
| Resize(count / 2); | |
| } | |
| return true; | |
| } | |
| void Clear() { | |
| std::lock_guard<std::mutex> lock(resize_mutex_); | |
| std::vector<std::unique_lock<std::mutex>> locks; | |
| locks.reserve(mutexes_.size()); | |
| for (auto&& mut : mutexes_) { | |
| locks.emplace_back(*mut); | |
| } | |
| table_.clear(); | |
| } | |
| size_t Size() const { | |
| std::lock_guard<std::mutex> lock(resize_mutex_); | |
| std::vector<std::unique_lock<std::mutex>> locks; | |
| locks.reserve(mutexes_.size()); | |
| for (auto&& mut : mutexes_) { | |
| locks.emplace_back(*mut); | |
| } | |
| return table_.size(); | |
| } | |
| std::pair<bool, V> Find(const K& key) const { | |
| auto hash = hasher_(key) % table_.size(); | |
| auto group = hash / (table_.size() / mutexes_.size()); | |
| std::lock_guard<std::mutex> lock(*mutexes_.at(group)); | |
| auto it = std::find_if(table_[hash].begin(), table_[hash].end(), | |
| [&key](const auto& pair) { return pair.first == key; }); | |
| return it == std::end(table_[hash]) ? std::make_pair(false, V()) | |
| : std::make_pair(true, it->second); | |
| } | |
| const V At(const K& key) const { | |
| auto hash = hasher_(key) % table_.size(); | |
| auto group = hash / (table_.size() / mutexes_.size()); | |
| std::lock_guard<std::mutex> lock(*mutexes_.at(group)); | |
| auto it = std::find_if(table_[hash].begin(), table_[hash].end(), | |
| [&key](const auto& pair) { return pair.first == key; }); | |
| return it->second; | |
| } | |
| static const int kDefaultConcurrencyLevel; | |
| static const int kUndefinedSize; | |
| private: | |
| void Resize(size_t newsize) { | |
| std::lock_guard<std::mutex> lock(resize_mutex_); | |
| std::vector<std::unique_lock<std::mutex>> locks; | |
| locks.reserve(mutexes_.size()); | |
| for (auto&& mut : mutexes_) { | |
| locks.emplace_back(*mut); | |
| } | |
| std::vector<std::forward_list<std::pair<K, V>>> newtable; | |
| newtable.reserve(newsize); | |
| for (const auto& bucket : table_) { | |
| for (const auto& pair : bucket) { | |
| auto hash = hasher_(pair.first) % newsize; | |
| newtable[hash].emplace_front(pair.first, pair.second); | |
| } | |
| } | |
| table_ = newtable; | |
| }; // globally locks | |
| size_t count = 0; | |
| Hash hasher_; | |
| std::vector<std::forward_list<std::pair<K, V>>> table_; | |
| mutable std::vector<std::unique_ptr<std::mutex>> mutexes_; | |
| mutable std::mutex resize_mutex_; | |
| }; | |
| template <class K, class V, class Hash> | |
| const int ConcurrentHashMap<K, V, Hash>::kDefaultConcurrencyLevel = 8; | |
| template <class K, class V, class Hash> | |
| const int ConcurrentHashMap<K, V, Hash>::kUndefinedSize = -1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment