From cd04334700b47855f26e0250ff94582597f115c5 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sun, 19 Nov 2023 16:19:47 -0500 Subject: [PATCH] Support `ReadWriteLock` in more parallel map APIs. --- include/gtl/phmap.hpp | 125 ++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/include/gtl/phmap.hpp b/include/gtl/phmap.hpp index c951d1d..18e578e 100644 --- a/include/gtl/phmap.hpp +++ b/include/gtl/phmap.hpp @@ -3626,14 +3626,6 @@ class parallel_hash_set { // ---------------------------------- // same as emplace, but hashval is provided // -------------------------------------------------------------------- - template - std::pair emplace_decomposable_with_hash(const K& key, size_t hashval, Args&&... args) { - Inner& inner = sets_[subidx(hashval)]; - auto& set = inner.set_; - UniqueLock m(inner); - return make_rv(&inner, set.emplace_decomposable(key, hashval, std::forward(args)...)); - } - struct EmplaceDecomposableHashval { template std::pair operator()(const K& key, Args&&... args) const { @@ -3681,25 +3673,34 @@ class parallel_hash_set { return emplace_with_hash(hashval, std::forward(args)...).first; } - template - iterator lazy_emplace_with_hash(const key_arg& key, size_t hashval, F&& f) { - Inner& inner = sets_[subidx(hashval)]; - auto& set = inner.set_; - UniqueLock m(inner); - return make_iterator(&inner, set.lazy_emplace_with_hash(key, hashval, std::forward(f))); - } - // -------------------------------------------------------------------- // end of phmap expension // -------------------------------------------------------------------- + template + std::pair emplace_decomposable_with_hash(const K& key, size_t hashval, Args&&... args) { + Inner& inner = sets_[subidx(hashval)]; + auto& set = inner.set_; + ReadWriteLock m(inner); + + size_t offset = set._find_key(key, hashval); + if (offset == (size_t)-1 && m.switch_to_unique()) { + // we did an unlock/lock, and another thread could have inserted the same key, so we need to + // do a find() again. + offset = set._find_key(key, hashval); + } + if (offset == (size_t)-1) { + offset = set.prepare_insert(hashval); + set.emplace_at(offset, std::forward(args)...); + set.set_ctrl(offset, H2(hashval)); + return make_rv(&inner, { set.iterator_at(offset), true }); + } + return make_rv(&inner, { set.iterator_at(offset), false }); + } + template std::pair emplace_decomposable(const K& key, Args&&... args) { - size_t hashval = this->hash(key); - Inner& inner = sets_[subidx(hashval)]; - auto& set = inner.set_; - UniqueLock m(inner); - return make_rv(&inner, set.emplace_decomposable(key, hashval, std::forward(args)...)); + return emplace_decomposable_with_hash(key, this->hash(key), std::forward(args)...); } struct EmplaceDecomposable { @@ -3736,10 +3737,11 @@ class parallel_hash_set { size_t hashval = this->hash(PolicyTraits::key(slot)); PolicyTraits::construct(&alloc_ref(), slot, std::forward(args)...); - const auto& elem = PolicyTraits::element(slot); - Inner& inner = sets_[subidx(hashval)]; - auto& set = inner.set_; - UniqueLock m(inner); + const auto& elem = PolicyTraits::element(slot); + Inner& inner = sets_[subidx(hashval)]; + auto& set = inner.set_; + UniqueLock m(inner); + typename EmbeddedSet::template InsertSlotWithHash f{ inner, std::move(*slot), hashval }; return make_rv(PolicyTraits::apply(f, elem)); } @@ -3761,13 +3763,28 @@ class parallel_hash_set { // lazy_emplace // ------------ - template + template + iterator lazy_emplace_with_hash(const key_arg& key, size_t hashval, F&& f) { + Inner& inner = sets_[subidx(hashval)]; + auto& set = inner.set_; + ReadWriteLock m(inner); + size_t offset = set._find_key(key, hashval); + if (offset == (size_t)-1 && m.switch_to_unique()) { + // we did an unlock/lock, and another thread could have inserted the same key, so we need to + // do a find() again. + offset = set._find_key(key, hashval); + } + if (offset == (size_t)-1) { + offset = set.prepare_insert(hashval); + set.lazy_emplace_at(offset, std::forward(f)); + set.set_ctrl(offset, H2(hashval)); + } + return iterator_at(offset); + } + + template iterator lazy_emplace(const key_arg& key, F&& f) { - auto hashval = this->hash(key); - Inner& inner = sets_[subidx(hashval)]; - auto& set = inner.set_; - UniqueLock m(inner); - return make_iterator(&inner, set.lazy_emplace_with_hash(key, hashval, std::forward(f))); + return lazy_emplace_with_hash(key, this->hash(key), std::forward(f)); } // emplace_single @@ -3837,18 +3854,25 @@ class parallel_hash_set { // ---------------------------------------------------------------------------------------------------- template bool erase_if(const key_arg& key, F&& f) { - return erase_if_impl(key, std::forward(f)); + return erase_if_impl(key, std::forward(f)); } - template + template bool erase_if_impl(const key_arg& key, F&& f) { - static_assert(std::is_invocable_v); + static_assert(std::is_invocable::value); auto hashval = this->hash(key); Inner& inner = sets_[subidx(hashval)]; auto& set = inner.set_; L m(inner); auto it = set.find(key, hashval); - if (it == set.end()) return false; + if (it == set.end()) + return false; + if (m.switch_to_unique()) { + // we did an unlock/lock, need to call `find()` again + it = set.find(key, hashval); + if (it == set.end()) + return false; + } if (std::forward(f)(const_cast(*it))) { set._erase(it); @@ -3870,6 +3894,7 @@ class parallel_hash_set { auto res = this->find_or_prepare_insert_with_hash(hashval, key, m); Inner* inner = std::get<0>(res); if (std::get<2>(res)) { + // key not found. call fEmplace lambda which should invoke passed constructor if constexpr (std::is_same_v) { inner->set_.lazy_emplace_at(std::get<1>(res), std::forward(fEmplace)); inner->set_.set_ctrl(std::get<1>(res), H2(hashval)); @@ -3880,8 +3905,8 @@ class parallel_hash_set { inner->set_.erase(*del); } } else { + // key found. Call fExists lambda. In case of the set, non "key" part of value_type can be changed auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res))); - // in case of the set, non "key" part of value_type can be changed if constexpr (std::is_same_v) std::forward(fExists)(const_cast(*it)); @@ -4326,19 +4351,14 @@ class parallel_hash_set { auto& set = inner.set_; mutexlock = std::move(typename Lockable::ReadWriteLock(inner)); size_t offset = set._find_key(key, hashval); + if (offset == (size_t)-1 && mutexlock.switch_to_unique()) { + // we did an unlock/lock, and another thread could have inserted the same key, so we need to + // do a find() again. + offset = set._find_key(key, hashval); + } if (offset == (size_t)-1) { - if (mutexlock.switch_to_unique()) { - // we did an unlock/lock, and another thread could have inserted the same key, so we need to - // do a find() again. - offset = set._find_key(key, hashval); - if (offset == (size_t)-1) { - offset = set.prepare_insert(hashval); - return std::make_tuple(&inner, offset, true); - } - } else { - offset = set.prepare_insert(hashval); - return std::make_tuple(&inner, offset, true); - } + offset = set.prepare_insert(hashval); + return std::make_tuple(&inner, offset, true); } return std::make_tuple(&inner, offset, false); } @@ -4555,9 +4575,10 @@ class parallel_hash_map : public parallel_hash_set bool try_emplace_l(K&& k, F&& f, Args&&... args) { size_t hashval = this->hash(k); - UniqueLock m; + ReadWriteLock m; auto res = this->find_or_prepare_insert_with_hash(hashval, k, m); typename Base::Inner* inner = std::get<0>(res); + if (std::get<2>(res)) { inner->set_.emplace_at(std::get<1>(res), std::piecewise_construct, @@ -4566,8 +4587,8 @@ class parallel_hash_map : public parallel_hash_setset_.set_ctrl(std::get<1>(res), H2(hashval)); } else { auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res))); - std::forward(f)( - const_cast(*it)); // in case of the set, non "key" part of value_type can be changed + // call lambda. in case of the set, non "key" part of value_type can be changed + std::forward(f)(const_cast(*it)); } return std::get<2>(res); } @@ -4578,7 +4599,7 @@ class parallel_hash_map : public parallel_hash_set std::pair try_emplace_p(K&& k, Args&&... args) { size_t hashval = this->hash(k); - UniqueLock m; + ReadWriteLock m; auto res = this->find_or_prepare_insert_with_hash(hashval, k, m); typename Base::Inner* inner = std::get<0>(res); if (std::get<2>(res)) {