diff --git a/parallel_hashmap/phmap.h b/parallel_hashmap/phmap.h index 4636533..5c59579 100644 --- a/parallel_hashmap/phmap.h +++ b/parallel_hashmap/phmap.h @@ -2929,7 +2929,7 @@ public: }; // -------------------------------------------------------------------- - // phmap expension: emplace_with_hash + // phmap extension: emplace_with_hash // ---------------------------------- // same as emplace, but hashval is provided // -------------------------------------------------------------------- @@ -3081,6 +3081,8 @@ public: return {iterator(inner, &sets_[0] + num_tables, res.first), res.second}; } + // lazy_emplace + // ------------ template iterator lazy_emplace(const key_arg& key, F&& f) { auto hashval = this->hash(key); @@ -3090,15 +3092,109 @@ public: return make_iterator(&inner, set.lazy_emplace_with_hash(key, hashval, std::forward(f))); } + // emplace_single + // -------------- template - void emplace_single(const key_arg& key, F&& f) { - auto hashval = this->hash(key); + void emplace_single_with_hash(const key_arg& key, size_t &hashval, F&& f) { Inner& inner = sets_[subidx(hashval)]; auto& set = inner.set_; typename Lockable::UniqueLock m(inner); set.emplace_single_with_hash(key, hashval, std::forward(f)); } + template + void emplace_single(const key_arg& key, F&& f) { + auto hashval = this->hash(key); + emplace_single_with_hash(key, hashval, std::forward(f)); + } + + // if set contains key, lambda is called with the value_type (under read lock protection), + // and if_contains returns true. This is a const API and lambda should not modify the value + // ----------------------------------------------------------------------------------------- + template + bool if_contains(const key_arg& key, F&& f) const { + return const_cast(this)->template + modify_if_impl(key, std::forward(f)); + } + + // if set contains key, lambda is called with the value_type without read lock protection, + // and if_contains_unsafe returns true. This is a const API and lambda should not modify the value + // This should be used only if we know that no other thread may be mutating the set at the time. + // ----------------------------------------------------------------------------------------- + template + bool if_contains_unsafe(const key_arg& key, F&& f) const { + return const_cast(this)->template + modify_if_impl::DoNothing>(key, std::forward(f)); + } + + // if map contains key, lambda is called with the value_type (under write lock protection), + // and modify_if returns true. This is a non-const API and lambda is allowed to modify the mapped value + // ---------------------------------------------------------------------------------------------------- + template + bool modify_if(const key_arg& key, F&& f) { + return modify_if_impl(key, std::forward(f)); + } + + // ----------------------------------------------------------------------------------------- + template + bool modify_if_impl(const key_arg& key, F&& f) { +#if __cplusplus >= 201703L + static_assert(std::is_invocable::value); +#endif + L m; + auto ptr = this->template find_ptr(key, this->hash(key), m); + if (ptr == nullptr) + return false; + std::forward(f)(*ptr); + return true; + } + + // if map contains key, lambda is called with the mapped value (under write lock protection). + // If the lambda returns true, the key is subsequently erased from the map (the write lock + // is only released after erase). + // returns true if key was erased, false otherwise. + // ---------------------------------------------------------------------------------------------------- + template + bool erase_if(const key_arg& key, F&& f) { + return erase_if_impl(key, std::forward(f)); + } + + template + bool erase_if_impl(const key_arg& key, F&& f) { +#if __cplusplus >= 201703L + static_assert(std::is_invocable::value); +#endif + L m; + auto it = this->template find(key, this->hash(key), m); + if (it == this->end()) return false; + if (std::forward(f)(const_cast(*it))) + { + this->erase(it); + return true; + } + return false; + } + + // if map does not contains key, it is inserted and the mapped value is value-constructed + // with the provided arguments (if any), as with try_emplace. + // if map already contains key, then the lambda is called with the mapped value (under + // write lock protection) and can update the mapped value. + // returns true if key was not already present, false otherwise. + // --------------------------------------------------------------------------------------- + template + bool lazy_emplace_l(const key_arg& key, FExists&& fExists, FEmplace&& fEmplace) { + typename Lockable::UniqueLock m; + auto res = this->find_or_prepare_insert(key, m); + Inner* inner = std::get<0>(res); + if (std::get<2>(res)) + inner->set_.lazy_emplace_at(std::get<1>(res), std::forward(fEmplace)); + else { + auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res))); + std::forward(fExists)(const_cast(*it)); // in case of the set, non "key" part of value_type can be changed + } + return std::get<2>(res); + } + // Extension API: support iterating over all values // // flat_hash_set s; @@ -3554,8 +3650,9 @@ class parallel_hash_map : public parallel_hash_set; public: - using key_type = typename Policy::key_type; + using key_type = typename Policy::key_type; using mapped_type = typename Policy::mapped_type; + using value_type = typename Base::value_type; template using key_arg = typename KeyArgImpl::template type; @@ -3692,43 +3789,6 @@ public: return try_emplace_with_hash(hashval, k, std::forward(args)...).first; } - // if map contains key, lambda is called with the mapped value (under read lock protection), - // and if_contains returns true. This is a const API and lambda should not modify the value - // ----------------------------------------------------------------------------------------- - template - bool if_contains(const key_arg& key, F&& f) const { - return const_cast(this)->template - modify_if_impl(key, std::forward(f)); - } - - // if map contains key, lambda is called with the mapped value without read lock protection, - // and if_contains_unsafe returns true. This is a const API and lambda should not modify the value - // This should be used only if we know that no other thread may be mutating the map at the time. - // ----------------------------------------------------------------------------------------- - template - bool if_contains_unsafe(const key_arg& key, F&& f) const { - return const_cast(this)->template - modify_if_impl::DoNothing>(key, std::forward(f)); - } - - // if map contains key, lambda is called with the mapped value (under write lock protection), - // and modify_if returns true. This is a non-const API and lambda is allowed to modify the mapped value - // ---------------------------------------------------------------------------------------------------- - template - bool modify_if(const key_arg& key, F&& f) { - return modify_if_impl(key, std::forward(f)); - } - - // if map contains key, lambda is called with the mapped value (under write lock protection). - // If the lambda returns true, the key is subsequently erased from the map (the write lock - // is only released after erase). - // returns true if key was erased, false otherwise. - // ---------------------------------------------------------------------------------------------------- - template - bool erase_if(const key_arg& key, F&& f) { - return erase_if_impl(key, std::forward(f)); - } - // if map does not contains key, it is inserted and the mapped value is value-constructed // with the provided arguments (if any), as with try_emplace. // if map already contains key, then the lambda is called with the mapped value (under @@ -3746,21 +3806,7 @@ public: std::forward_as_tuple(std::forward(args)...)); else { auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res))); - std::forward(f)(Policy::value(&*it)); - } - return std::get<2>(res); - } - - template - bool lazy_emplace_l(const key_arg& key, FExists&& fExists, FEmplace&& fEmplace) { - typename Lockable::UniqueLock m; - auto res = this->find_or_prepare_insert(key, m); - typename Base::Inner* inner = std::get<0>(res); - if (std::get<2>(res)) - inner->set_.lazy_emplace_at(std::get<1>(res), std::forward(fEmplace)); - else { - auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res))); - std::forward(fExists)(Policy::value(&*it)); + std::forward(f)(const_cast(*it)); // in case of the set, non "key" part of value_type can be changed } return std::get<2>(res); } @@ -3778,35 +3824,6 @@ public: } private: - template - bool modify_if_impl(const key_arg& key, F&& f) { -#if __cplusplus >= 201703L - static_assert(std::is_invocable::value); -#endif - L m; - auto ptr = this->template find_ptr(key, this->hash(key), m); - if (ptr == nullptr) - return false; - std::forward(f)(Policy::value(ptr)); - return true; - } - - template - bool erase_if_impl(const key_arg& key, F&& f) { -#if __cplusplus >= 201703L - static_assert(std::is_invocable::value); -#endif - L m; - auto it = this->template find(key, this->hash(key), m); - if (it == this->end()) return false; - if (std::forward(f)(Policy::value(&*it))) - { - this->erase(it); - return true; - } - return false; - } - template std::pair insert_or_assign_impl(K&& k, V&& v) { diff --git a/tests/parallel_hash_map_test.cc b/tests/parallel_hash_map_test.cc index b4b9d96..a7ef1ee 100644 --- a/tests/parallel_hash_map_test.cc +++ b/tests/parallel_hash_map_test.cc @@ -9,131 +9,132 @@ namespace phmap { namespace priv { namespace { -TEST(THIS_TEST_NAME, ThreadSafeContains) { - // We can't test mutable keys, or non-copyable keys with ThisMap. - // Test that the nodes have the proper API. +TEST(THIS_TEST_NAME, IfContains) { + // ---------------- + // test if_contains + // ---------------- + using Map = ThisMap; - - { - // ---------------- - // test if_contains - // ---------------- - Map m = { {1, 7}, {2, 9} }; - const Map& const_m(m); + Map m = { {1, 7}, {2, 9} }; + const Map& const_m(m); - auto val = 0; - auto get_value = [&val](const int& v) { val = v; }; - EXPECT_TRUE(const_m.if_contains(2, get_value)); - EXPECT_EQ(val, 9); + auto val = 0; + auto get_value = [&val](const Map::value_type& v) { val = v.second; }; + EXPECT_TRUE(const_m.if_contains(2, get_value)); + EXPECT_EQ(val, 9); - EXPECT_FALSE(m.if_contains(3, get_value)); - } + EXPECT_FALSE(m.if_contains(3, get_value)); +} - { - // -------------- - // test modify_if - // -------------- - Map m = { {1, 7}, {2, 9} }; +TEST(THIS_TEST_NAME, ModifyIf) { + // -------------- + // test modify_if + // -------------- + using Map = ThisMap; + Map m = { {1, 7}, {2, 9} }; - auto set_value = [](int& v) { v = 11; }; - EXPECT_TRUE(m.modify_if(2, set_value)); - EXPECT_EQ(m[2], 11); + auto set_value = [](Map::value_type& v) { v.second = 11; }; + EXPECT_TRUE(m.modify_if(2, set_value)); + EXPECT_EQ(m[2], 11); - EXPECT_FALSE(m.modify_if(3, set_value)); // because m[3] does not exist - } + EXPECT_FALSE(m.modify_if(3, set_value)); // because m[3] does not exist +} - { - // ------------------ - // test try_emplace_l - // ------------------ - Map m = { {1, 7}, {2, 9} }; +TEST(THIS_TEST_NAME, TryEmplaceL) { + // ------------------ + // test try_emplace_l + // ------------------ + using Map = ThisMap; + Map m = { {1, 7}, {2, 9} }; - // overwrite an existing value - m.try_emplace_l(2, [](int& v) { v = 5; }); - EXPECT_EQ(m[2], 5); + // overwrite an existing value + m.try_emplace_l(2, [](Map::value_type& v) { v.second = 5; }); + EXPECT_EQ(m[2], 5); - // insert a value that is not already present. Will be default initialised to 0 and lambda not called - m.try_emplace_l(3, - [](int& v) { v = 6; }, // called only when key was already present - 1); // argument to construct new value is key not present - EXPECT_EQ(m[3], 1); + // insert a value that is not already present. Will be default initialised to 0 and lambda not called + m.try_emplace_l(3, + [](Map::value_type& v) { v.second = 6; }, // called only when key was already present + 1); // argument to construct new value is key not present + EXPECT_EQ(m[3], 1); - // insert a value that is not already present, provide argument to value-construct it - m.try_emplace_l(4, - [](int& ) {}, // called only when key was already present - 999); // argument to construct new value is key not present + // insert a value that is not already present, provide argument to value-construct it + m.try_emplace_l(4, + [](Map::value_type& ) {}, // called only when key was already present + 999); // argument to construct new value is key not present - EXPECT_EQ(m[4], 999); - } + EXPECT_EQ(m[4], 999); +} - { - // -------------------- - // test lazy_emplace_l - // -------------------- - Map m = { {1, 7}, {2, 9} }; +TEST(THIS_TEST_NAME, LazyEmplaceL) { + // -------------------- + // test lazy_emplace_l + // -------------------- + using Map = ThisMap; + Map m = { {1, 7}, {2, 9} }; - // insert a value that is not already present. - // right now m[5] does not exist - m.lazy_emplace_l(5, - [](int& v) { v = 6; }, // called only when key was already present - [](const Map::constructor& ctor) { ctor(5, 13); }); // construct value_type in place when key not present + // insert a value that is not already present. + // right now m[5] does not exist + m.lazy_emplace_l(5, + [](Map::value_type& v) { v.second = 6; }, // called only when key was already present + [](const Map::constructor& ctor) { ctor(5, 13); }); // construct value_type in place when key not present + EXPECT_EQ(m[5], 13); - EXPECT_EQ(m[5], 13); + // change a value that is present. Currently m[5] == 13 + m.lazy_emplace_l(5, + [](Map::value_type& v) { v.second = 6; }, // called only when key was already present + [](const Map::constructor& ctor) { ctor(5, 13); }); // construct value_type in place when key not present + EXPECT_EQ(m[5], 6); +} - // change a value that is present. Currently m[5] == 13 - m.lazy_emplace_l(5, - [](int& v) { v = 6; }, // called only when key was already present - [](const Map::constructor& ctor) { ctor(5, 13); }); // construct value_type in place when key not present - EXPECT_EQ(m[5], 6); - } +TEST(THIS_TEST_NAME, EraseIf) { + // ------------- + // test erase_if + // ------------- + using Map = ThisMap; + Map m = { {1, 7}, {2, 9}, {5, 6} }; - { - // ------------- - // test erase_if - // ------------- - Map m = { {1, 7}, {2, 9}, {5, 6} }; + EXPECT_EQ(m.erase_if(9, [](Map::value_type& v) { assert(0); return v.second == 12; }), false); // m[9] not present - lambda not called + EXPECT_EQ(m.erase_if(5, [](Map::value_type& v) { return v.second == 12; }), false); // m[5] == 6, so erase not performed + EXPECT_EQ(m[5], 6); + EXPECT_EQ(m.erase_if(5, [](Map::value_type& v) { return v.second == 6; }), true); // lambda returns true, so m[5] erased + EXPECT_EQ(m[5], 0); +} - EXPECT_EQ(m.erase_if(9, [](int& v) { assert(0); return v==12; }), false); // m[9] not present - lambda not called - EXPECT_EQ(m.erase_if(5, [](int& v) { return v==12; }), false); // m[5] == 6, so erase not performed - EXPECT_EQ(m[5], 6); - EXPECT_EQ(m.erase_if(5, [](int& v) { return v==6; }), true); // lambda returns true, so m[5] erased - EXPECT_EQ(m[5], 0); - } +TEST(THIS_TEST_NAME, ForEach) { + // ------------- + // test for_each + // ------------- + using Map = ThisMap; + Map m = { {1, 7}, {2, 8}, {5, 11} }; - { - // ------------- - // test for_each - // ------------- - Map m = { {1, 7}, {2, 8}, {5, 11} }; + // increment all values by 1 + m.for_each_m([](Map::value_type &pair) { ++pair.second; }); - // increment all values by 1 - m.for_each_m([](Map::value_type &pair) { ++pair.second; }); - - int counter = 0; - m.for_each([&counter](const Map::value_type &pair) { + int counter = 0; + m.for_each([&counter](const Map::value_type &pair) { ++counter; EXPECT_EQ(pair.first + 7, pair.second); }); - EXPECT_EQ(counter, 3); - } - - { - // -------------------- - // test emplace_single - // -------------------- - Map m = { {1, 4}, {11, 4} }; - - // emplace_single insert a value if not already present, else removes it - for (int i=0; i<12; ++i) - m.emplace_single(i, [i](const Map::constructor& ctor) { ctor(i, 4); }); - EXPECT_EQ(m.count(0), 1); - EXPECT_EQ(m.count(1), 0); - EXPECT_EQ(m.count(2), 1); - EXPECT_EQ(m.count(11), 0); - } - + EXPECT_EQ(counter, 3); } +TEST(THIS_TEST_NAME, EmplaceSingle) { + // -------------------- + // test emplace_single + // -------------------- + using Map = ThisMap; + Map m = { {1, 4}, {11, 4} }; + + // emplace_single insert a value if not already present, else removes it + for (int i=0; i<12; ++i) + m.emplace_single(i, [i](const Map::constructor& ctor) { ctor(i, 4); }); + EXPECT_EQ(m.count(0), 1); + EXPECT_EQ(m.count(1), 0); + EXPECT_EQ(m.count(2), 1); + EXPECT_EQ(m.count(11), 0); +} + + } // namespace } // namespace priv } // namespace phmap diff --git a/tests/parallel_hash_set_test.cc b/tests/parallel_hash_set_test.cc index 0f0143b..8e69e60 100644 --- a/tests/parallel_hash_set_test.cc +++ b/tests/parallel_hash_set_test.cc @@ -9,26 +9,124 @@ namespace phmap { namespace priv { namespace { -TEST(THIS_TEST_NAME, ThreadSafeContains) { - // We can't test mutable keys, or non-copyable keys with ThisSet. - // Test that the nodes have the proper API. - using Set = phmap::THIS_HASH_SET; - - { - // -------------------- - // test emplace_single - // -------------------- - Set m = { {1}, {11} }; - - // emplace_single insert a value if not already present, else removes it - for (int i=0; i<12; ++i) - m.emplace_single(i, [i](const Set::constructor& ctor) { ctor(i); }); - EXPECT_EQ(m.count(0), 1); - EXPECT_EQ(m.count(1), 0); - EXPECT_EQ(m.count(2), 1); - EXPECT_EQ(m.count(11), 0); +struct Entry +{ + bool operator==(const Entry &o) const + { + return key == o.key; // not checking value } + // Demonstrates how to provide the hash function as a friend member function of the class + // This can be used as an alternative to providing a std::hash specialization + // -------------------------------------------------------------------------------------- + friend size_t hash_value(const Entry &p) + { + return phmap::HashState().combine(0, p.key); // not checking value + } + + int key; + int value; +}; + +TEST(THIS_TEST_NAME, IfContains) { + // ---------------- + // test if_contains + // ---------------- + using Set = phmap::THIS_HASH_SET; + Set m = { {1, 7}, {2, 9} }; + const Set& const_m(m); + + auto val = 0; + auto get_value = [&val](const Set::value_type& v) { val = v.value; }; + EXPECT_TRUE(const_m.if_contains(Entry{2}, get_value)); + EXPECT_EQ(val, 9); + + EXPECT_FALSE(m.if_contains(Entry{3}, get_value)); +} + +TEST(THIS_TEST_NAME, ModifyIf) { + // -------------- + // test modify_if + // -------------- + using Set = phmap::THIS_HASH_SET; + Set m = { {1, 7}, {2, 9} }; + + auto set_value = [](Set::value_type& v) { v.value = 11; }; + EXPECT_TRUE(m.modify_if(Entry{2}, set_value)); + + auto val = 0; + auto get_value = [&val](const Set::value_type& v) { val = v.value; }; + EXPECT_TRUE(m.if_contains(Entry{2}, get_value)); + EXPECT_EQ(val, 11); + + EXPECT_FALSE(m.modify_if(Entry{3}, set_value)); // because m[3] does not exist +} + +TEST(THIS_TEST_NAME, LazyEmplaceL) { + // -------------------- + // test lazy_emplace_l + // -------------------- + using Set = phmap::THIS_HASH_SET; + Set m = { {1, 7}, {2, 9} }; + + // insert a value that is not already present. + // right now m[5] does not exist + m.lazy_emplace_l(Entry{5}, + [](Set::value_type& v) { v.value = 6; }, // called only when key was already present + [](const Set::constructor& ctor) { ctor(5, 13); }); // construct value_type in place when key not present + EXPECT_EQ(m.find(Entry{5})->value, 13); + + // change a value that is present. + m.lazy_emplace_l(Entry{5}, + [](Set::value_type& v) { v.value = 6; }, // called only when key was already present + [](const Set::constructor& ctor) { ctor(5, 13); }); // construct value_type in place when key not present + EXPECT_EQ(m.find(Entry{5})->value, 6); +} + +TEST(THIS_TEST_NAME, EraseIf) { + // ------------- + // test erase_if + // ------------- + using Set = phmap::THIS_HASH_SET; + Set m = { {1, 7}, {2, 9}, {5, 6} }; + + EXPECT_EQ(m.erase_if(Entry{9}, [](Set::value_type& v) { assert(0); return v.value == 12; }), false); // m[9] not present - lambda not called + EXPECT_EQ(m.erase_if(Entry{5}, [](Set::value_type& v) { return v.value == 12; }), false); // m[5] == 6, so erase not performed + EXPECT_EQ(m.find(Entry{5})->value, 6); + EXPECT_EQ(m.erase_if(Entry{5}, [](Set::value_type& v) { return v.value == 6; }), true); // lambda returns true, so m[5] erased + EXPECT_EQ(m.find(Entry{5}), m.end()); +} + +TEST(THIS_TEST_NAME, ForEach) { + // ------------- + // test for_each + // ------------- + using Set = phmap::THIS_HASH_SET; + Set m = { {1, 7}, {2, 8}, {5, 11} }; + + int counter = 0; + m.for_each([&counter](const Set::value_type &v) { + ++counter; + EXPECT_EQ(v.key + 6, v.value); + }); + EXPECT_EQ(counter, 3); +} + +TEST(THIS_TEST_NAME, EmplaceSingle) { + using Set = phmap::THIS_HASH_SET; + + // -------------------- + // test emplace_single + // -------------------- + Set m = { {1}, {11} }; + + // emplace_single insert a value if not already present, else removes it + for (int i=0; i<12; ++i) + m.emplace_single(i, [i](const Set::constructor& ctor) { ctor(i); }); + EXPECT_EQ(m.count(0), 1); + EXPECT_EQ(m.count(1), 0); + EXPECT_EQ(m.count(2), 1); + EXPECT_EQ(m.count(11), 0); } } // namespace