support most extended APIs for sets as we do for maps.

This commit is contained in:
greg
2022-01-22 21:15:55 -05:00
parent 6893d3c259
commit 43c4c86479
3 changed files with 320 additions and 204 deletions
+102 -85
View File
@@ -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 <class K = key_type, class F>
iterator lazy_emplace(const key_arg<K>& 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>(f)));
}
// emplace_single
// --------------
template <class K = key_type, class F>
void emplace_single(const key_arg<K>& key, F&& f) {
auto hashval = this->hash(key);
void emplace_single_with_hash(const key_arg<K>& 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>(f));
}
template <class K = key_type, class F>
void emplace_single(const key_arg<K>& key, F&& f) {
auto hashval = this->hash(key);
emplace_single_with_hash<K, F>(key, hashval, std::forward<F>(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 <class K = key_type, class F>
bool if_contains(const key_arg<K>& key, F&& f) const {
return const_cast<parallel_hash_set*>(this)->template
modify_if_impl<K, F, typename Lockable::SharedLock>(key, std::forward<F>(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 <class K = key_type, class F>
bool if_contains_unsafe(const key_arg<K>& key, F&& f) const {
return const_cast<parallel_hash_set*>(this)->template
modify_if_impl<K, F, LockableBaseImpl<phmap::NullMutex>::DoNothing>(key, std::forward<F>(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 <class K = key_type, class F>
bool modify_if(const key_arg<K>& key, F&& f) {
return modify_if_impl<K, F, typename Lockable::UniqueLock>(key, std::forward<F>(f));
}
// -----------------------------------------------------------------------------------------
template <class K = key_type, class F, class L>
bool modify_if_impl(const key_arg<K>& key, F&& f) {
#if __cplusplus >= 201703L
static_assert(std::is_invocable<F, value_type&>::value);
#endif
L m;
auto ptr = this->template find_ptr<K, L>(key, this->hash(key), m);
if (ptr == nullptr)
return false;
std::forward<F>(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 <class K = key_type, class F>
bool erase_if(const key_arg<K>& key, F&& f) {
return erase_if_impl<K, F, typename Lockable::UniqueLock>(key, std::forward<F>(f));
}
template <class K = key_type, class F, class L>
bool erase_if_impl(const key_arg<K>& key, F&& f) {
#if __cplusplus >= 201703L
static_assert(std::is_invocable<F, value_type&>::value);
#endif
L m;
auto it = this->template find<K, L>(key, this->hash(key), m);
if (it == this->end()) return false;
if (std::forward<F>(f)(const_cast<value_type &>(*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 <class K = key_type, class FExists, class FEmplace>
bool lazy_emplace_l(const key_arg<K>& 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>(fEmplace));
else {
auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res)));
std::forward<FExists>(fExists)(const_cast<value_type &>(*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<std::string> s;
@@ -3554,8 +3650,9 @@ class parallel_hash_map : public parallel_hash_set<N, RefSet, Mtx_, Policy, Hash
using Lockable = phmap::LockableImpl<Mtx_>;
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 <class K>
using key_arg = typename KeyArgImpl::template type<K, key_type>;
@@ -3692,43 +3789,6 @@ public:
return try_emplace_with_hash(hashval, k, std::forward<Args>(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 <class K = key_type, class F>
bool if_contains(const key_arg<K>& key, F&& f) const {
return const_cast<parallel_hash_map*>(this)->template
modify_if_impl<K, F, typename Lockable::SharedLock>(key, std::forward<F>(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 <class K = key_type, class F>
bool if_contains_unsafe(const key_arg<K>& key, F&& f) const {
return const_cast<parallel_hash_map*>(this)->template
modify_if_impl<K, F, LockableBaseImpl<phmap::NullMutex>::DoNothing>(key, std::forward<F>(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 <class K = key_type, class F>
bool modify_if(const key_arg<K>& key, F&& f) {
return modify_if_impl<K, F, typename Lockable::UniqueLock>(key, std::forward<F>(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 <class K = key_type, class F>
bool erase_if(const key_arg<K>& key, F&& f) {
return erase_if_impl<K, F, typename Lockable::UniqueLock>(key, std::forward<F>(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>(args)...));
else {
auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res)));
std::forward<F>(f)(Policy::value(&*it));
}
return std::get<2>(res);
}
template <class K = key_type, class FExists, class FEmplace>
bool lazy_emplace_l(const key_arg<K>& 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>(fEmplace));
else {
auto it = this->iterator_at(inner, inner->set_.iterator_at(std::get<1>(res)));
std::forward<FExists>(fExists)(Policy::value(&*it));
std::forward<F>(f)(const_cast<value_type &>(*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 <class K = key_type, class F, class L>
bool modify_if_impl(const key_arg<K>& key, F&& f) {
#if __cplusplus >= 201703L
static_assert(std::is_invocable<F, mapped_type&>::value);
#endif
L m;
auto ptr = this->template find_ptr<K, L>(key, this->hash(key), m);
if (ptr == nullptr)
return false;
std::forward<F>(f)(Policy::value(ptr));
return true;
}
template <class K = key_type, class F, class L>
bool erase_if_impl(const key_arg<K>& key, F&& f) {
#if __cplusplus >= 201703L
static_assert(std::is_invocable<F, mapped_type&>::value);
#endif
L m;
auto it = this->template find<K, L>(key, this->hash(key), m);
if (it == this->end()) return false;
if (std::forward<F>(f)(Policy::value(&*it)))
{
this->erase(it);
return true;
}
return false;
}
template <class K, class V>
std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v) {
+102 -101
View File
@@ -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<int, int>;
{
// ----------------
// 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<int, int>;
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<int, int>;
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<int, int>;
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<int, int>;
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<int, int>;
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<int, int>;
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
+116 -18
View File
@@ -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<int>;
{
// --------------------
// 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<Person> 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<Entry>;
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<Entry>;
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<Entry>;
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<Entry>;
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<Entry>;
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<int>;
// --------------------
// 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