Commit 103ad3da authored by Dave Watson's avatar Dave Watson Committed by Facebook Github Bot

Support movable keys

Summary: Use the same trick as the values, so that non-copyable keys can be used in ConcurrentHashMap.

Reviewed By: yfeldblum

Differential Revision: D6252711

fbshipit-source-id: f0f168c4eb361d372bdfc3417f32222d66c11aaf
parent 897877b8
...@@ -201,25 +201,27 @@ class ConcurrentHashMap { ...@@ -201,25 +201,27 @@ class ConcurrentHashMap {
return res; return res;
} }
std::pair<ConstIterator, bool> insert(const KeyType& k, const ValueType& v) { template <typename Key, typename Value>
std::pair<ConstIterator, bool> insert(Key&& k, Value&& v) {
auto segment = pickSegment(k); auto segment = pickSegment(k);
std::pair<ConstIterator, bool> res( std::pair<ConstIterator, bool> res(
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(this, segment), std::forward_as_tuple(this, segment),
std::forward_as_tuple(false)); std::forward_as_tuple(false));
res.second = ensureSegment(segment)->insert(res.first.it_, k, v); res.second = ensureSegment(segment)->insert(
res.first.it_, std::forward<Key>(k), std::forward<Value>(v));
return res; return res;
} }
template <typename... Args> template <typename Key, typename... Args>
std::pair<ConstIterator, bool> try_emplace(const KeyType& k, Args&&... args) { std::pair<ConstIterator, bool> try_emplace(Key&& k, Args&&... args) {
auto segment = pickSegment(k); auto segment = pickSegment(k);
std::pair<ConstIterator, bool> res( std::pair<ConstIterator, bool> res(
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(this, segment), std::forward_as_tuple(this, segment),
std::forward_as_tuple(false)); std::forward_as_tuple(false));
res.second = ensureSegment(segment)->try_emplace( res.second = ensureSegment(segment)->try_emplace(
res.first.it_, k, std::forward<Args>(args)...); res.first.it_, std::forward<Key>(k), std::forward<Args>(args)...);
return res; return res;
} }
...@@ -242,26 +244,28 @@ class ConcurrentHashMap { ...@@ -242,26 +244,28 @@ class ConcurrentHashMap {
return res; return res;
} }
std::pair<ConstIterator, bool> insert_or_assign( template <typename Key, typename Value>
const KeyType& k, std::pair<ConstIterator, bool> insert_or_assign(Key&& k, Value&& v) {
const ValueType& v) {
auto segment = pickSegment(k); auto segment = pickSegment(k);
std::pair<ConstIterator, bool> res( std::pair<ConstIterator, bool> res(
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(this, segment), std::forward_as_tuple(this, segment),
std::forward_as_tuple(false)); std::forward_as_tuple(false));
res.second = ensureSegment(segment)->insert_or_assign(res.first.it_, k, v); res.second = ensureSegment(segment)->insert_or_assign(
res.first.it_, std::forward<Key>(k), std::forward<Value>(v));
return res; return res;
} }
folly::Optional<ConstIterator> assign(const KeyType& k, const ValueType& v) { template <typename Key, typename Value>
folly::Optional<ConstIterator> assign(Key&& k, Value&& v) {
auto segment = pickSegment(k); auto segment = pickSegment(k);
ConstIterator res(this, segment); ConstIterator res(this, segment);
auto seg = segments_[segment].load(std::memory_order_acquire); auto seg = segments_[segment].load(std::memory_order_acquire);
if (!seg) { if (!seg) {
return folly::Optional<ConstIterator>(); return folly::Optional<ConstIterator>();
} else { } else {
auto r = seg->assign(res.it_, k, v); auto r =
seg->assign(res.it_, std::forward<Key>(k), std::forward<Value>(v));
if (!r) { if (!r) {
return folly::Optional<ConstIterator>(); return folly::Optional<ConstIterator>();
} }
...@@ -270,17 +274,20 @@ class ConcurrentHashMap { ...@@ -270,17 +274,20 @@ class ConcurrentHashMap {
} }
// Assign to desired if and only if key k is equal to expected // Assign to desired if and only if key k is equal to expected
folly::Optional<ConstIterator> assign_if_equal( template <typename Key, typename Value>
const KeyType& k, folly::Optional<ConstIterator>
const ValueType& expected, assign_if_equal(Key&& k, const ValueType& expected, Value&& desired) {
const ValueType& desired) {
auto segment = pickSegment(k); auto segment = pickSegment(k);
ConstIterator res(this, segment); ConstIterator res(this, segment);
auto seg = segments_[segment].load(std::memory_order_acquire); auto seg = segments_[segment].load(std::memory_order_acquire);
if (!seg) { if (!seg) {
return folly::Optional<ConstIterator>(); return folly::Optional<ConstIterator>();
} else { } else {
auto r = seg->assign_if_equal(res.it_, k, expected, desired); auto r = seg->assign_if_equal(
res.it_,
std::forward<Key>(k),
expected,
std::forward<Value>(desired));
if (!r) { if (!r) {
return folly::Optional<ConstIterator>(); return folly::Optional<ConstIterator>();
} }
......
...@@ -47,11 +47,11 @@ class ValueHolder { ...@@ -47,11 +47,11 @@ class ValueHolder {
explicit ValueHolder(const ValueHolder& other) : item_(other.item_) {} explicit ValueHolder(const ValueHolder& other) : item_(other.item_) {}
template <typename... Args> template <typename Arg, typename... Args>
ValueHolder(const KeyType& k, Args&&... args) ValueHolder(std::piecewise_construct_t, Arg&& k, Args&&... args)
: item_( : item_(
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(k), std::forward_as_tuple(std::forward<Arg>(k)),
std::forward_as_tuple(std::forward<Args>(args)...)) {} std::forward_as_tuple(std::forward<Args>(args)...)) {}
value_type& getItem() { value_type& getItem() {
return item_; return item_;
...@@ -69,7 +69,9 @@ class ValueHolder< ...@@ -69,7 +69,9 @@ class ValueHolder<
KeyType, KeyType,
ValueType, ValueType,
Allocator, Allocator,
std::enable_if_t<!std::is_nothrow_copy_constructible<ValueType>::value>> { std::enable_if_t<
!std::is_nothrow_copy_constructible<ValueType>::value ||
!std::is_nothrow_copy_constructible<KeyType>::value>> {
public: public:
typedef std::pair<const KeyType, ValueType> value_type; typedef std::pair<const KeyType, ValueType> value_type;
...@@ -78,12 +80,12 @@ class ValueHolder< ...@@ -78,12 +80,12 @@ class ValueHolder<
item_ = other.item_; item_ = other.item_;
} }
template <typename... Args> template <typename Arg, typename... Args>
ValueHolder(const KeyType& k, Args&&... args) { ValueHolder(std::piecewise_construct_t, Arg&& k, Args&&... args) {
item_ = (value_type*)Allocator().allocate(sizeof(value_type)); item_ = (value_type*)Allocator().allocate(sizeof(value_type));
new (item_) value_type( new (item_) value_type(
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(k), std::forward_as_tuple(std::forward<Arg>(k)),
std::forward_as_tuple(std::forward<Args>(args)...)); std::forward_as_tuple(std::forward<Args>(args)...));
} }
...@@ -116,9 +118,12 @@ class NodeT : public folly::hazptr::hazptr_obj_base< ...@@ -116,9 +118,12 @@ class NodeT : public folly::hazptr::hazptr_obj_base<
explicit NodeT(NodeT* other) : item_(other->item_) {} explicit NodeT(NodeT* other) : item_(other->item_) {}
template <typename... Args> template <typename Arg, typename... Args>
NodeT(const KeyType& k, Args&&... args) NodeT(Arg&& k, Args&&... args)
: item_(k, std::forward<Args>(args)...) {} : item_(
std::piecewise_construct,
std::forward<Arg>(k),
std::forward<Args>(args)...) {}
/* Nodes are refcounted: If a node is retired() while a writer is /* Nodes are refcounted: If a node is retired() while a writer is
traversing the chain, the rest of the chain must remain valid traversing the chain, the rest of the chain must remain valid
...@@ -244,19 +249,20 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment { ...@@ -244,19 +249,20 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment {
} }
bool insert(Iterator& it, std::pair<key_type, mapped_type>&& foo) { bool insert(Iterator& it, std::pair<key_type, mapped_type>&& foo) {
return insert(it, foo.first, foo.second); return insert(it, std::move(foo.first), std::move(foo.second));
} }
bool insert(Iterator& it, const KeyType& k, const ValueType& v) { template <typename Key, typename Value>
bool insert(Iterator& it, Key&& k, Value&& v) {
auto node = (Node*)Allocator().allocate(sizeof(Node)); auto node = (Node*)Allocator().allocate(sizeof(Node));
new (node) Node(k, v); new (node) Node(std::forward<Key>(k), std::forward<Value>(v));
auto res = insert_internal( auto res = insert_internal(
it, it,
k, node->getItem().first,
InsertType::DOES_NOT_EXIST, InsertType::DOES_NOT_EXIST,
[](const ValueType&) { return false; }, [](const ValueType&) { return false; },
node, node,
v); node);
if (!res) { if (!res) {
node->~Node(); node->~Node();
Allocator().deallocate((uint8_t*)node, sizeof(Node)); Allocator().deallocate((uint8_t*)node, sizeof(Node));
...@@ -264,14 +270,17 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment { ...@@ -264,14 +270,17 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment {
return res; return res;
} }
template <typename... Args> template <typename Key, typename... Args>
bool try_emplace(Iterator& it, const KeyType& k, Args&&... args) { bool try_emplace(Iterator& it, Key&& k, Args&&... args) {
// Note: first key is only ever compared. Second is moved in to
// create the node, and the first key is never touched again.
return insert_internal( return insert_internal(
it, it,
k, std::forward<Key>(k),
InsertType::DOES_NOT_EXIST, InsertType::DOES_NOT_EXIST,
[](const ValueType&) { return false; }, [](const ValueType&) { return false; },
nullptr, nullptr,
std::forward<Key>(k),
std::forward<Args>(args)...); std::forward<Args>(args)...);
} }
...@@ -282,29 +291,39 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment { ...@@ -282,29 +291,39 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment {
k, k,
InsertType::DOES_NOT_EXIST, InsertType::DOES_NOT_EXIST,
[](const ValueType&) { return false; }, [](const ValueType&) { return false; },
node,
node); node);
} }
bool insert_or_assign(Iterator& it, const KeyType& k, const ValueType& v) { template <typename Key, typename Value>
return insert_internal( bool insert_or_assign(Iterator& it, Key&& k, Value&& v) {
auto node = (Node*)Allocator().allocate(sizeof(Node));
new (node) Node(std::forward<Key>(k), std::forward<Value>(v));
auto res = insert_internal(
it, it,
k, node->getItem().first,
InsertType::ANY, InsertType::ANY,
[](const ValueType&) { return false; }, [](const ValueType&) { return false; },
nullptr, node,
v); node);
if (!res) {
node->~Node();
Allocator().deallocate((uint8_t*)node, sizeof(Node));
}
return res;
} }
bool assign(Iterator& it, const KeyType& k, const ValueType& v) { template <typename Key, typename Value>
bool assign(Iterator& it, Key&& k, Value&& v) {
auto node = (Node*)Allocator().allocate(sizeof(Node)); auto node = (Node*)Allocator().allocate(sizeof(Node));
new (node) Node(k, v); new (node) Node(std::forward<Key>(k), std::forward<Value>(v));
auto res = insert_internal( auto res = insert_internal(
it, it,
k, node->getItem().first,
InsertType::MUST_EXIST, InsertType::MUST_EXIST,
[](const ValueType&) { return false; }, [](const ValueType&) { return false; },
node, node,
v); node);
if (!res) { if (!res) {
node->~Node(); node->~Node();
Allocator().deallocate((uint8_t*)node, sizeof(Node)); Allocator().deallocate((uint8_t*)node, sizeof(Node));
...@@ -312,18 +331,26 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment { ...@@ -312,18 +331,26 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment {
return res; return res;
} }
template <typename Key, typename Value>
bool assign_if_equal( bool assign_if_equal(
Iterator& it, Iterator& it,
const KeyType& k, Key&& k,
const ValueType& expected, const ValueType& expected,
const ValueType& desired) { Value&& desired) {
return insert_internal( auto node = (Node*)Allocator().allocate(sizeof(Node));
new (node) Node(std::forward<Key>(k), std::forward<Value>(desired));
auto res = insert_internal(
it, it,
k, node->getItem().first,
InsertType::MATCH, InsertType::MATCH,
[expected](const ValueType& v) { return v == expected; }, [&expected](const ValueType& v) { return v == expected; },
nullptr, node,
desired); node);
if (!res) {
node->~Node();
Allocator().deallocate((uint8_t*)node, sizeof(Node));
}
return res;
} }
template <typename MatchFunc, typename... Args> template <typename MatchFunc, typename... Args>
...@@ -369,7 +396,7 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment { ...@@ -369,7 +396,7 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment {
} else { } else {
if (!cur) { if (!cur) {
cur = (Node*)Allocator().allocate(sizeof(Node)); cur = (Node*)Allocator().allocate(sizeof(Node));
new (cur) Node(k, std::forward<Args>(args)...); new (cur) Node(std::forward<Args>(args)...);
} }
auto next = node->next_.load(std::memory_order_relaxed); auto next = node->next_.load(std::memory_order_relaxed);
cur->next_.store(next, std::memory_order_relaxed); cur->next_.store(next, std::memory_order_relaxed);
...@@ -415,7 +442,7 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment { ...@@ -415,7 +442,7 @@ class FOLLY_ALIGNED(64) ConcurrentHashMapSegment {
// OR DOES_NOT_EXIST, but only in the try_emplace case // OR DOES_NOT_EXIST, but only in the try_emplace case
DCHECK(type == InsertType::ANY || type == InsertType::DOES_NOT_EXIST); DCHECK(type == InsertType::ANY || type == InsertType::DOES_NOT_EXIST);
cur = (Node*)Allocator().allocate(sizeof(Node)); cur = (Node*)Allocator().allocate(sizeof(Node));
new (cur) Node(k, std::forward<Args>(args)...); new (cur) Node(std::forward<Args>(args)...);
} }
cur->next_.store(headnode, std::memory_order_relaxed); cur->next_.store(headnode, std::memory_order_relaxed);
head->store(cur, std::memory_order_release); head->store(cur, std::memory_order_release);
......
...@@ -114,7 +114,8 @@ int foo::copied{0}; ...@@ -114,7 +114,8 @@ int foo::copied{0};
TEST(ConcurrentHashMap, EmplaceTest) { TEST(ConcurrentHashMap, EmplaceTest) {
ConcurrentHashMap<uint64_t, foo> foomap(200); ConcurrentHashMap<uint64_t, foo> foomap(200);
foomap.insert(1, foo()); foo bar; // Make sure to test copy
foomap.insert(1, bar);
EXPECT_EQ(foo::moved, 0); EXPECT_EQ(foo::moved, 0);
EXPECT_EQ(foo::copied, 1); EXPECT_EQ(foo::copied, 1);
foo::copied = 0; foo::copied = 0;
...@@ -151,12 +152,21 @@ TEST(ConcurrentHashMap, MapResizeTest) { ...@@ -151,12 +152,21 @@ TEST(ConcurrentHashMap, MapResizeTest) {
// Ensure we can insert objects without copy constructors. // Ensure we can insert objects without copy constructors.
TEST(ConcurrentHashMap, MapNoCopiesTest) { TEST(ConcurrentHashMap, MapNoCopiesTest) {
struct Uncopyable { struct Uncopyable {
int i_;
Uncopyable(int i) { Uncopyable(int i) {
(void*)&i; i_ = i;
} }
Uncopyable(const Uncopyable& that) = delete; Uncopyable(const Uncopyable& that) = delete;
bool operator==(const Uncopyable& o) const {
return i_ == o.i_;
}
};
struct Hasher {
size_t operator()(const Uncopyable&) const {
return 0;
}
}; };
ConcurrentHashMap<uint64_t, Uncopyable> foomap(2); ConcurrentHashMap<Uncopyable, Uncopyable, Hasher> foomap(2);
EXPECT_TRUE(foomap.try_emplace(1, 1).second); EXPECT_TRUE(foomap.try_emplace(1, 1).second);
EXPECT_TRUE(foomap.try_emplace(2, 2).second); EXPECT_TRUE(foomap.try_emplace(2, 2).second);
auto res = foomap.find(2); auto res = foomap.find(2);
...@@ -169,6 +179,37 @@ TEST(ConcurrentHashMap, MapNoCopiesTest) { ...@@ -169,6 +179,37 @@ TEST(ConcurrentHashMap, MapNoCopiesTest) {
EXPECT_EQ(&(res->second), &(res2->second)); EXPECT_EQ(&(res->second), &(res2->second));
} }
TEST(ConcurrentHashMap, MapMovableKeysTest) {
struct Movable {
int i_;
Movable(int i) {
i_ = i;
}
Movable(const Movable&) = delete;
Movable(Movable&& o) {
i_ = o.i_;
o.i_ = 0;
}
bool operator==(const Movable& o) const {
return i_ == o.i_;
}
};
struct Hasher {
size_t operator()(const Movable&) const {
return 0;
}
};
ConcurrentHashMap<Movable, Movable, Hasher> foomap(2);
EXPECT_TRUE(foomap.insert(std::make_pair(Movable(10), Movable(1))).second);
EXPECT_TRUE(foomap.assign(Movable(10), Movable(2)));
EXPECT_TRUE(foomap.insert(Movable(11), Movable(1)).second);
EXPECT_TRUE(foomap.emplace(Movable(12), Movable(1)).second);
EXPECT_TRUE(foomap.insert_or_assign(Movable(10), Movable(3)).second);
EXPECT_TRUE(foomap.assign_if_equal(Movable(10), Movable(3), Movable(4)));
EXPECT_FALSE(foomap.try_emplace(Movable(10), Movable(3)).second);
EXPECT_TRUE(foomap.try_emplace(Movable(13), Movable(3)).second);
}
TEST(ConcurrentHashMap, MapUpdateTest) { TEST(ConcurrentHashMap, MapUpdateTest) {
ConcurrentHashMap<uint64_t, uint64_t> foomap(2); ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
EXPECT_TRUE(foomap.insert(1, 10).second); EXPECT_TRUE(foomap.insert(1, 10).second);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment