Commit 74e8af86 authored by Nathan Bronson's avatar Nathan Bronson Committed by Facebook GitHub Bot

add heterogeneous lookup and eraseInto to fallback F14 sets

Summary:
This diff adds heterogeneous lookup support and eraseInto to the
fallback (non-SIMD) version of F14 sets, which is used on mobile.
std::is_invocable<hasher, const_iterator const&> is no longer evaluated
during erase.  It also restructures F14MapTest and F14SetTest so that
as many of the tests as possible run in the fallback mode.

(Note: this ignores all push blocking failures!)

Reviewed By: yfeldblum, ot

Differential Revision: D21489440

fbshipit-source-id: 872a250f8a6ec3c9efcd19edb7d9beca196fa865
parent ce0007aa
......@@ -70,15 +70,20 @@ class F14BasicMap {
K>::value,
T>;
template <typename K>
using IsIter = Disjunction<
std::is_same<typename Policy::Iter, remove_cvref_t<K>>,
std::is_same<typename Policy::ConstIter, remove_cvref_t<K>>>;
template <typename K, typename T>
using EnableHeterogeneousErase = std::enable_if_t<
EligibleForHeterogeneousFind<
typename Policy::Value,
typename Policy::Key,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value &&
!std::is_same<typename Policy::Iter, remove_cvref_t<K>>::value &&
!std::is_same<typename Policy::ConstIter, remove_cvref_t<K>>::value,
std::conditional_t<IsIter<K>::value, typename Policy::Key, K>>::
value &&
!IsIter<K>::value,
T>;
public:
......@@ -973,19 +978,21 @@ class F14VectorMapImpl : public F14BasicMap<MapPolicyWithDefaults<
private:
using Super = F14BasicMap<Policy>;
template <typename K>
using IsIter = Disjunction<
std::is_same<typename Policy::Iter, remove_cvref_t<K>>,
std::is_same<typename Policy::ConstIter, remove_cvref_t<K>>,
std::is_same<typename Policy::ReverseIter, remove_cvref_t<K>>,
std::is_same<typename Policy::ConstReverseIter, remove_cvref_t<K>>>;
template <typename K, typename T>
using EnableHeterogeneousVectorErase = std::enable_if_t<
EligibleForHeterogeneousFind<
typename Policy::Value,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value &&
!std::is_same<typename Policy::Iter, remove_cvref_t<K>>::value &&
!std::is_same<typename Policy::ConstIter, remove_cvref_t<K>>::value &&
!std::is_same<typename Policy::ReverseIter, remove_cvref_t<K>>::
value &&
!std::is_same<typename Policy::ConstReverseIter, remove_cvref_t<K>>::
value,
Key,
Hasher,
KeyEqual,
std::conditional_t<IsIter<K>::value, Key, K>>::value &&
!IsIter<K>::value,
T>;
public:
......
......@@ -66,14 +66,18 @@ class F14BasicSet {
K>::value,
T>;
template <typename K>
using IsIter = std::is_same<typename Policy::Iter, remove_cvref_t<K>>;
template <typename K, typename T>
using EnableHeterogeneousErase = std::enable_if_t<
EligibleForHeterogeneousFind<
typename Policy::Value,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value &&
!std::is_same<typename Policy::Iter, remove_cvref_t<K>>::value,
std::conditional_t<IsIter<K>::value, typename Policy::Value, K>>::
value &&
!IsIter<K>::value,
T>;
public:
......@@ -743,15 +747,20 @@ class F14VectorSetImpl : public F14BasicSet<SetPolicyWithDefaults<
private:
using Super = F14BasicSet<Policy>;
template <typename K>
using IsIter = Disjunction<
std::is_same<typename Policy::Iter, remove_cvref_t<K>>,
std::is_same<typename Policy::ReverseIter, remove_cvref_t<K>>>;
template <typename K, typename T>
using EnableHeterogeneousVectorErase = std::enable_if_t<
EligibleForHeterogeneousFind<
typename Policy::Value,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value &&
!std::is_same<typename Policy::Iter, remove_cvref_t<K>>::value &&
!std::is_same<typename Policy::ReverseIter, remove_cvref_t<K>>::value,
std::conditional_t<IsIter<K>::value, typename Policy::Value, K>>::
value &&
!IsIter<K>::value,
T>;
public:
......
......@@ -22,26 +22,295 @@
* required SIMD instructions, based on std::unordered_set.
*/
#include <algorithm>
#include <unordered_set>
namespace folly {
namespace f14 {
namespace detail {
template <typename K, typename H, typename E, typename A>
class F14BasicSet : public std::unordered_set<K, H, E, A> {
using Super = std::unordered_set<K, H, E, A>;
template <typename KeyType, typename Hasher, typename KeyEqual, typename Alloc>
class F14BasicSet
: public std::unordered_set<KeyType, Hasher, KeyEqual, Alloc> {
using Super = std::unordered_set<KeyType, Hasher, KeyEqual, Alloc>;
public:
using typename Super::allocator_type;
using typename Super::const_iterator;
using typename Super::hasher;
using typename Super::iterator;
using typename Super::key_equal;
using typename Super::key_type;
using typename Super::pointer;
using typename Super::size_type;
using typename Super::value_type;
private:
template <typename K, typename T>
using EnableHeterogeneousFind = std::enable_if_t<
EligibleForHeterogeneousFind<key_type, hasher, key_equal, K>::value,
T>;
template <typename K, typename T>
using EnableHeterogeneousInsert = std::enable_if_t<
EligibleForHeterogeneousInsert<key_type, hasher, key_equal, K>::value,
T>;
template <typename K>
using IsIter = Disjunction<
std::is_same<iterator, remove_cvref_t<K>>,
std::is_same<const_iterator, remove_cvref_t<K>>>;
template <typename K, typename T>
using EnableHeterogeneousErase = std::enable_if_t<
EligibleForHeterogeneousFind<
key_type,
hasher,
key_equal,
std::conditional_t<IsIter<K>::value, key_type, K>>::value &&
!IsIter<K>::value,
T>;
public:
F14BasicSet() = default;
using Super::Super;
//// PUBLIC - Modifiers
using Super::insert;
template <typename K>
EnableHeterogeneousInsert<K, std::pair<iterator, bool>> insert(K&& value) {
return emplace(std::forward<K>(value));
}
template <class InputIt>
void insert(InputIt first, InputIt last) {
while (first != last) {
insert(*first);
++first;
}
}
private:
template <typename Arg>
using UsableAsKey =
EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
public:
template <class... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
auto a = this->get_allocator();
return folly::detail::callWithConstructedKey<key_type, UsableAsKey>(
a,
[&](auto const&, auto&& key) {
if (!std::is_same<key_type, remove_cvref_t<decltype(key)>>::value) {
// this is a heterogeneous emplace
auto it = find(key);
if (it != this->end()) {
return std::make_pair(it, false);
}
auto rv = Super::emplace(std::forward<decltype(key)>(key));
FOLLY_SAFE_CHECK(
rv.second, "post-find emplace should always insert");
return rv;
} else {
return Super::emplace(std::forward<decltype(key)>(key));
}
},
std::forward<Args>(args)...);
}
template <class... Args>
iterator emplace_hint(const_iterator /*hint*/, Args&&... args) {
return emplace(std::forward<Args>(args)...).first;
}
using Super::erase;
template <typename K>
EnableHeterogeneousErase<K, size_type> erase(K const& key) {
auto it = find(key);
if (it != this->end()) {
erase(it);
return 1;
} else {
return 0;
}
}
//// PUBLIC - Lookup
private:
template <typename K>
struct BottomKeyEqual {
[[noreturn]] bool operator()(K const&, K const&) const {
FOLLY_SAFE_CHECK(false, "bucket should not invoke key equality");
assume_unreachable();
}
};
template <typename Iter, typename Self, typename K>
static Iter findImpl(Self& self, K const& key) {
if (self.empty()) {
return self.end();
}
using A = typename std::allocator_traits<
allocator_type>::template rebind_alloc<K>;
using E = BottomKeyEqual<K>;
// this is exceedingly wicked!
auto slot =
reinterpret_cast<std::unordered_set<K, hasher, E, A> const&>(self)
.bucket(key);
auto b = self.begin(slot);
auto e = self.end(slot);
while (b != e) {
if (self.key_eq()(key, *b)) {
Iter it;
static_assert(sizeof(it) <= sizeof(b), "");
std::memcpy(&it, &b, sizeof(it));
FOLLY_SAFE_CHECK(
std::addressof(*b) == std::addressof(*it),
"ABI-assuming local_iterator to iterator conversion failed");
return it;
}
++b;
}
FOLLY_SAFE_DCHECK(
self.size() > 3 ||
std::none_of(
self.begin(),
self.end(),
[&](auto const& k) { return self.key_eq()(key, k); }),
"");
return self.end();
}
public:
using Super::count;
template <typename K>
EnableHeterogeneousFind<K, size_type> count(K const& key) const {
return contains(key) ? 1 : 0;
}
using Super::find;
template <typename K>
EnableHeterogeneousFind<K, iterator> find(K const& key) {
return findImpl<iterator>(*this, key);
}
template <typename K>
EnableHeterogeneousFind<K, const_iterator> find(K const& key) const {
return findImpl<const_iterator>(*this, key);
}
bool contains(key_type const& key) const {
return find(key) != this->end();
}
template <typename K>
EnableHeterogeneousFind<K, bool> contains(K const& key) const {
return find(key) != this->end();
}
private:
template <typename Self, typename K>
static auto equalRangeImpl(Self& self, K const& key) {
auto first = self.find(key);
auto last = first;
if (last != self.end()) {
++last;
}
return std::make_pair(first, last);
}
public:
using Super::equal_range;
template <typename K>
EnableHeterogeneousFind<K, std::pair<iterator, iterator>> equal_range(
K const& key) {
return equalRangeImpl(*this, key);
}
template <typename K>
EnableHeterogeneousFind<K, std::pair<const_iterator, const_iterator>>
equal_range(K const& key) const {
return equalRangeImpl(*this, key);
}
//// PUBLIC - F14 Extensions
#if FOLLY_F14_ERASE_INTO_AVAILABLE
private:
// converts const_iterator to iterator when they are different types
// such as in libstdc++
template <typename... Args>
iterator citerToIter(const_iterator cit, Args&&...) {
iterator it = erase(cit, cit);
FOLLY_SAFE_CHECK(std::addressof(*it) == std::addressof(*cit), "");
return it;
}
// converts const_iterator to iterator when they are the same type
// such as in libc++
iterator citerToIter(iterator it) {
return it;
}
public:
template <typename BeforeDestroy>
iterator eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
iterator next = citerToIter(pos);
++next;
auto nh = this->extract(pos);
if (!nh.empty()) {
beforeDestroy(std::move(nh.value()));
}
return next;
}
template <typename BeforeDestroy>
iterator eraseInto(
const_iterator first,
const_iterator last,
BeforeDestroy&& beforeDestroy) {
iterator pos = citerToIter(first);
while (pos != last) {
pos = eraseInto(pos, beforeDestroy);
}
return pos;
}
private:
template <typename K, typename BeforeDestroy>
size_type eraseIntoImpl(K const& key, BeforeDestroy& beforeDestroy) {
auto it = find(key);
if (it != this->end()) {
eraseInto(it, beforeDestroy);
return 1;
} else {
return 0;
}
}
public:
template <typename BeforeDestroy>
size_type eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
return eraseIntoImpl(key, beforeDestroy);
}
template <typename K, typename BeforeDestroy>
EnableHeterogeneousErase<K, size_type> eraseInto(
K const& key,
BeforeDestroy&& beforeDestroy) {
return eraseIntoImpl(key, beforeDestroy);
}
#endif
bool containsEqualValue(value_type const& value) const {
// bucket is only valid if bucket_count is non-zero
if (this->empty()) {
......@@ -73,7 +342,8 @@ class F14BasicSet : public std::unordered_set<K, H, E, A> {
visitor(bc * sizeof(pointer), 1);
}
if (this->size() > 0) {
visitor(sizeof(StdNodeReplica<K, value_type, H>), this->size());
visitor(
sizeof(StdNodeReplica<key_type, value_type, hasher>), this->size());
}
}
......
......@@ -81,12 +81,18 @@
#include <xmmintrin.h> // _mm_prefetch
#endif
#ifndef FOLLY_F14_PERTURB_INSERTION_ORDER
#define FOLLY_F14_PERTURB_INSERTION_ORDER folly::kIsDebug
#endif
#else // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
#ifndef FOLLY_F14_PERTURB_INSERTION_ORDER
#define FOLLY_F14_PERTURB_INSERTION_ORDER folly::kIsDebug
#define FOLLY_F14_PERTURB_INSERTION_ORDER false
#endif
#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
namespace folly {
struct F14TableStats {
......
......@@ -17,6 +17,10 @@
#include <folly/container/F14Map.h>
#include <algorithm>
#include <chrono>
#include <random>
#include <string>
#include <typeinfo>
#include <unordered_map>
#include <glog/logging.h>
......@@ -26,13 +30,26 @@
#include <folly/FBString.h>
#include <folly/container/test/F14TestUtil.h>
#include <folly/container/test/TrackingTypes.h>
#include <folly/hash/Hash.h>
#include <folly/portability/GTest.h>
#include <folly/test/TestUtils.h>
using namespace folly;
using namespace folly::f14;
using namespace folly::string_piece_literals;
using namespace folly::test;
static constexpr bool kFallback = folly::f14::detail::getF14IntrinsicsMode() ==
folly::f14::detail::F14IntrinsicsMode::None;
template <typename T>
void runSanityChecks(T const& t) {
(void)t;
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
F14TableStats::compute(t);
#endif
}
template <template <typename, typename, typename, typename, typename>
class TMap>
void testCustomSwap() {
......@@ -41,23 +58,21 @@ void testCustomSwap() {
TMap<
int,
int,
folly::f14::DefaultHasher<int>,
folly::f14::DefaultKeyEqual<int>,
folly::test::SwapTrackingAlloc<std::pair<int const, int>>>
DefaultHasher<int>,
DefaultKeyEqual<int>,
SwapTrackingAlloc<std::pair<int const, int>>>
m0, m1;
folly::test::resetTracking();
resetTracking();
swap(m0, m1);
EXPECT_EQ(
0,
folly::test::Tracked<0>::counts().dist(folly::test::Counts{0, 0, 0, 0}));
EXPECT_EQ(0, Tracked<0>::counts().dist(Counts{0, 0, 0, 0}));
}
TEST(F14Map, customSwap) {
testCustomSwap<folly::F14ValueMap>();
testCustomSwap<folly::F14NodeMap>();
testCustomSwap<folly::F14VectorMap>();
testCustomSwap<folly::F14FastMap>();
testCustomSwap<F14ValueMap>();
testCustomSwap<F14NodeMap>();
testCustomSwap<F14VectorMap>();
testCustomSwap<F14FastMap>();
}
template <
......@@ -65,9 +80,6 @@ template <
typename K,
typename V>
void runAllocatedMemorySizeTest() {
using namespace folly::f14;
using namespace folly::f14::detail;
using namespace folly::test;
using A = SwapTrackingAlloc<std::pair<const K, V>>;
resetTracking();
......@@ -77,9 +89,7 @@ void runAllocatedMemorySizeTest() {
// if F14 intrinsics are not available then we fall back to using
// std::unordered_map underneath, but in that case the allocation
// info is only best effort
bool preciseAllocInfo = getF14IntrinsicsMode() != F14IntrinsicsMode::None;
if (preciseAllocInfo) {
if (!kFallback) {
EXPECT_EQ(testAllocatedMemorySize(), 0);
EXPECT_EQ(m.getAllocatedMemorySize(), 0);
}
......@@ -87,9 +97,9 @@ void runAllocatedMemorySizeTest() {
auto emptyMapAllocatedBlockCount = testAllocatedBlockCount();
for (size_t i = 0; i < 1000; ++i) {
m.insert(std::make_pair(folly::to<K>(i), V{}));
m.erase(folly::to<K>(i / 10 + 2));
if (preciseAllocInfo) {
m.insert(std::make_pair(to<K>(i), V{}));
m.erase(to<K>(i / 10 + 2));
if (!kFallback) {
EXPECT_EQ(testAllocatedMemorySize(), m.getAllocatedMemorySize());
}
EXPECT_GE(m.getAllocatedMemorySize(), sizeof(std::pair<K, V>) * m.size());
......@@ -100,7 +110,7 @@ void runAllocatedMemorySizeTest() {
size += bytes * n;
count += n;
});
if (preciseAllocInfo) {
if (!kFallback) {
EXPECT_EQ(testAllocatedMemorySize(), size);
EXPECT_EQ(testAllocatedBlockCount(), count);
}
......@@ -113,7 +123,7 @@ void runAllocatedMemorySizeTest() {
m.reserve(5);
EXPECT_GT(testAllocatedMemorySize(), 0);
m = {};
if (preciseAllocInfo) {
if (!kFallback) {
EXPECT_GT(testAllocatedMemorySize(), 0);
}
}
......@@ -123,10 +133,10 @@ void runAllocatedMemorySizeTest() {
template <typename K, typename V>
void runAllocatedMemorySizeTests() {
runAllocatedMemorySizeTest<folly::F14ValueMap, K, V>();
runAllocatedMemorySizeTest<folly::F14NodeMap, K, V>();
runAllocatedMemorySizeTest<folly::F14VectorMap, K, V>();
runAllocatedMemorySizeTest<folly::F14FastMap, K, V>();
runAllocatedMemorySizeTest<F14ValueMap, K, V>();
runAllocatedMemorySizeTest<F14NodeMap, K, V>();
runAllocatedMemorySizeTest<F14VectorMap, K, V>();
runAllocatedMemorySizeTest<F14FastMap, K, V>();
}
TEST(F14Map, getAllocatedMemorySize) {
......@@ -136,7 +146,7 @@ TEST(F14Map, getAllocatedMemorySize) {
runAllocatedMemorySizeTests<double, std::string>();
runAllocatedMemorySizeTests<std::string, int>();
runAllocatedMemorySizeTests<std::string, std::string>();
runAllocatedMemorySizeTests<folly::fbstring, long>();
runAllocatedMemorySizeTests<fbstring, long>();
}
template <typename M>
......@@ -144,7 +154,7 @@ void runVisitContiguousRangesTest(int n) {
M map;
for (int i = 0; i < n; ++i) {
folly::makeUnpredictable(i);
makeUnpredictable(i);
map[i] = i;
map.erase(i / 2);
}
......@@ -177,27 +187,27 @@ void runVisitContiguousRangesTest() {
}
TEST(F14ValueMap, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14ValueMap<int, int>>();
runVisitContiguousRangesTest<F14ValueMap<int, int>>();
}
TEST(F14NodeMap, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14NodeMap<int, int>>();
runVisitContiguousRangesTest<F14NodeMap<int, int>>();
}
TEST(F14VectorMap, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14VectorMap<int, int>>();
runVisitContiguousRangesTest<F14VectorMap<int, int>>();
}
TEST(F14FastMap, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14FastMap<int, int>>();
runVisitContiguousRangesTest<F14FastMap<int, int>>();
}
#if FOLLY_HAS_MEMORY_RESOURCE
TEST(F14Map, pmr_empty) {
folly::pmr::F14ValueMap<int, int> m1;
folly::pmr::F14NodeMap<int, int> m2;
folly::pmr::F14VectorMap<int, int> m3;
folly::pmr::F14FastMap<int, int> m4;
pmr::F14ValueMap<int, int> m1;
pmr::F14NodeMap<int, int> m2;
pmr::F14VectorMap<int, int> m3;
pmr::F14FastMap<int, int> m4;
EXPECT_TRUE(m1.empty() && m2.empty() && m3.empty() && m4.empty());
}
#endif
......@@ -224,9 +234,9 @@ template <typename N>
std::size_t NestedHash::operator()(N const& v) const {
std::size_t rv = 0;
for (auto& kv : *v.map_) {
rv += folly::Hash{}(operator()(kv.first), kv.second);
rv += Hash{}(operator()(kv.first), kv.second);
}
return folly::Hash{}(rv);
return Hash{}(rv);
}
template <template <class...> class TMap>
......@@ -254,18 +264,8 @@ void testNestedMapEquality() {
template <template <class...> class TMap>
void testEqualityRefinement() {
TMap<
std::pair<int, int>,
int,
folly::test::HashFirst,
folly::test::EqualFirst>
m1;
TMap<
std::pair<int, int>,
int,
folly::test::HashFirst,
folly::test::EqualFirst>
m2;
TMap<std::pair<int, int>, int, HashFirst, EqualFirst> m1;
TMap<std::pair<int, int>, int, HashFirst, EqualFirst> m2;
m1[std::make_pair(0, 0)] = 0;
m1[std::make_pair(1, 1)] = 1;
EXPECT_FALSE(m1.insert(std::make_pair(std::make_pair(0, 2), 0)).second);
......@@ -284,260 +284,19 @@ void testEqualityRefinement() {
} // namespace
TEST(F14Map, nestedMapEquality) {
testNestedMapEquality<folly::F14ValueMap>();
testNestedMapEquality<folly::F14NodeMap>();
testNestedMapEquality<folly::F14VectorMap>();
testNestedMapEquality<folly::F14FastMap>();
testNestedMapEquality<F14ValueMap>();
testNestedMapEquality<F14NodeMap>();
testNestedMapEquality<F14VectorMap>();
testNestedMapEquality<F14FastMap>();
}
TEST(F14Map, equalityRefinement) {
testEqualityRefinement<folly::F14ValueMap>();
testEqualityRefinement<folly::F14NodeMap>();
testEqualityRefinement<folly::F14VectorMap>();
testEqualityRefinement<folly::F14FastMap>();
}
template <typename M>
void runHeterogeneousInsertTest() {
M map;
resetTracking();
EXPECT_EQ(map.count(10), 0);
EXPECT_FALSE(map.contains(10));
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
resetTracking();
map[10] = 20;
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 1}), 0)
<< Tracked<1>::counts();
resetTracking();
std::pair<int, int> p(10, 30);
std::vector<std::pair<int, int>> v({p});
map[10] = 30;
map.insert(std::pair<int, int>(10, 30));
map.insert(std::pair<int const, int>(10, 30));
map.insert(p);
map.insert(v.begin(), v.end());
map.insert(
std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
map.insert_or_assign(10, 40);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
resetTracking();
map.emplace(10, 30);
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(10),
std::forward_as_tuple(30));
map.emplace(p);
map.try_emplace(10, 30);
map.try_emplace(10);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
resetTracking();
map.erase(10);
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
#if FOLLY_F14_ERASE_INTO_AVAILABLE
map.emplace(10, 40);
resetTracking();
map.eraseInto(10, [](auto&&, auto&&) {});
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
#endif
}
template <typename M>
void runHeterogeneousInsertStringTest() {
using P = std::pair<StringPiece, std::string>;
using CP = std::pair<const StringPiece, std::string>;
M map;
P p{"foo", "hello"};
std::vector<P> v{p};
StringPiece foo{"foo"};
map.insert(P("foo", "hello"));
// TODO(T31574848): the list-initialization below does not work on libstdc++
// versions (e.g., GCC < 6) with no implementation of N4387 ("perfect
// initialization" for pairs and tuples).
// StringPiece sp{"foo"};
// map.insert({sp, "hello"});
map.insert({"foo", "hello"});
map.insert(CP("foo", "hello"));
map.insert(p);
map.insert(v.begin(), v.end());
map.insert(
std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
map.insert_or_assign("foo", "hello");
map.insert_or_assign(StringPiece{"foo"}, "hello");
EXPECT_EQ(map["foo"], "hello");
map.emplace(StringPiece{"foo"}, "hello");
map.emplace("foo", "hello");
map.emplace(p);
map.emplace();
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(StringPiece{"foo"}),
std::forward_as_tuple(/* count */ 20, 'x'));
map.try_emplace(StringPiece{"foo"}, "hello");
map.try_emplace(foo, "hello");
map.try_emplace(foo);
map.try_emplace("foo");
map.try_emplace("foo", "hello");
map.try_emplace("foo", /* count */ 20, 'x');
map.erase(StringPiece{"foo"});
map.erase(foo);
map.erase("");
EXPECT_TRUE(map.empty());
}
TEST(F14ValueMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14ValueMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14ValueMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14ValueMap<std::string, std::string>>();
}
TEST(F14NodeMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14NodeMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14NodeMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14NodeMap<std::string, std::string>>();
}
TEST(F14VectorMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14VectorMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14VectorMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14VectorMap<std::string, std::string>>();
}
TEST(F14FastMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14FastMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14FastMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14FastMap<std::string, std::string>>();
testEqualityRefinement<F14ValueMap>();
testEqualityRefinement<F14NodeMap>();
testEqualityRefinement<F14VectorMap>();
testEqualityRefinement<F14FastMap>();
}
namespace {
// std::is_convertible is not transitive :( Problem scenario: B<T> is
// implicitly convertible to A, so hasher that takes A can be used as a
// transparent hasher for a map with key of type B<T>. C is implicitly
// convertible to any B<T>, but we have to disable heterogeneous find
// for C. There is no way to infer the T of the intermediate type so C
// can't be used to explicitly construct A.
struct A {
int value;
bool operator==(A const& rhs) const {
return value == rhs.value;
}
bool operator!=(A const& rhs) const {
return !(*this == rhs);
}
};
struct AHasher {
std::size_t operator()(A const& v) const {
return v.value;
}
};
template <typename T>
struct B {
int value;
explicit B(int v) : value(v) {}
/* implicit */ B(A const& v) : value(v.value) {}
/* implicit */ operator A() const {
return A{value};
}
};
struct C {
int value;
template <typename T>
/* implicit */ operator B<T>() const {
return B<T>{value};
}
};
} // namespace
TEST(F14FastMap, disabledDoubleTransparent) {
static_assert(std::is_convertible<B<char>, A>::value, "");
static_assert(std::is_convertible<C, B<char>>::value, "");
static_assert(!std::is_convertible<C, A>::value, "");
F14FastMap<
B<char>,
int,
folly::transparent<AHasher>,
folly::transparent<std::equal_to<A>>>
map;
map[A{10}] = 10;
EXPECT_TRUE(map.find(C{10}) != map.end());
EXPECT_TRUE(map.find(C{20}) == map.end());
}
///////////////////////////////////
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
#include <chrono>
#include <random>
#include <string>
#include <typeinfo>
#include <unordered_map>
#include <folly/hash/Hash.h>
using namespace folly;
using namespace folly::f14;
namespace {
std::string s(char const* p) {
return p;
......@@ -554,12 +313,16 @@ void runSimple() {
{{"abc", "first"}, {"abc", "second"}});
h.insert(v.begin(), v.begin());
EXPECT_EQ(h.size(), 0);
if (!kFallback) {
EXPECT_EQ(h.bucket_count(), 0);
}
h.insert(v.begin(), v.end());
EXPECT_EQ(h.size(), 1);
EXPECT_EQ(h["abc"], s("first"));
h = T{};
if (!kFallback) {
EXPECT_EQ(h.bucket_count(), 0);
}
h.insert(std::make_pair(s("abc"), s("ABC")));
EXPECT_TRUE(h.find(s("def")) == h.end());
......@@ -665,15 +428,15 @@ void runSimple() {
expectH8((h8 = std::move(h2)));
expectH8((h8 = {}));
F14TableStats::compute(h);
F14TableStats::compute(h2);
F14TableStats::compute(h3);
F14TableStats::compute(h4);
F14TableStats::compute(h5);
F14TableStats::compute(h6);
F14TableStats::compute(h7);
F14TableStats::compute(h8);
F14TableStats::compute(h9);
runSanityChecks(h);
runSanityChecks(h2);
runSanityChecks(h3);
runSanityChecks(h4);
runSanityChecks(h5);
runSanityChecks(h6);
runSanityChecks(h7);
runSanityChecks(h8);
runSanityChecks(h9);
}
template <typename T>
......@@ -710,12 +473,12 @@ void runRehash() {
for (unsigned i = 0; i < n; ++i) {
h.insert(std::make_pair(to<std::string>(i), s("")));
if (b != h.bucket_count()) {
F14TableStats::compute(h);
runSanityChecks(h);
b = h.bucket_count();
}
}
EXPECT_EQ(h.size(), n);
F14TableStats::compute(h);
runSanityChecks(h);
}
// T should be a map from uint64_t to Tracked<1> that uses SwapTrackingAlloc
......@@ -737,11 +500,15 @@ void runRandom() {
std::size_t resizingSmallRollbacks = 0;
std::size_t resizingLargeRollbacks = 0;
for (std::size_t reps = 0; reps < 100000 || rollbacks < 10 ||
resizingSmallRollbacks < 1 || resizingLargeRollbacks < 1;
for (std::size_t reps = 0; reps < 100000 ||
(!kFallback &&
(rollbacks < 10 || resizingSmallRollbacks < 1 ||
resizingLargeRollbacks < 1));
++reps) {
if (pctDist(gen) < 20) {
// 10% chance allocator will fail after 0 to 3 more allocations
if (!kFallback && pctDist(gen) < 20) {
// 10% chance allocator will fail after 0 to 3 more allocations.
// Skip rollback tests for fallback impl because gcc 4.9's
// libstdc++ doesn't pass them.
limitTestAllocations(gen() & 3);
} else {
unlimitTestAllocations();
......@@ -934,7 +701,7 @@ void runRandom() {
EXPECT_EQ((t0 == t1), (r0 == r1));
} else if (pct < 99) {
// clear
F14TableStats::compute(t0);
runSanityChecks(t0);
t0.clear();
r0.clear();
} else if (pct < 100) {
......@@ -950,7 +717,7 @@ void runRandom() {
} catch (std::bad_alloc const&) {
++rollbacks;
F14TableStats::compute(t0);
runSanityChecks(t0);
if (leakCheckOnly) {
unlimitTestAllocations();
......@@ -982,23 +749,6 @@ void runRandom() {
EXPECT_EQ(testAllocatedMemorySize(), 0);
}
template <typename T>
void runPrehash() {
T h;
EXPECT_EQ(h.size(), 0);
h.insert(std::make_pair(s("abc"), s("ABC")));
EXPECT_TRUE(h.find(s("def")) == h.end());
EXPECT_FALSE(h.find(s("abc")) == h.end());
auto t1 = h.prehash(s("def"));
F14HashToken t2;
t2 = h.prehash(s("abc"));
EXPECT_TRUE(h.find(t1, s("def")) == h.end());
EXPECT_FALSE(h.find(t2, s("abc")) == h.end());
}
TEST(F14ValueMap, simple) {
runSimple<F14ValueMap<std::string, std::string>>();
}
......@@ -1033,6 +783,7 @@ TEST(F14FastMap, pmr_simple) {
}
#endif
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
TEST(F14VectorMap, reverse_iterator) {
using TMap = F14VectorMap<uint64_t, uint64_t>;
auto populate = [](TMap& h, uint64_t lo, uint64_t hi) {
......@@ -1086,6 +837,7 @@ TEST(F14VectorMap, OrderPreservingReinsertionView) {
EXPECT_EQ(asVector(m1), asVector(m2));
}
#endif
TEST(F14ValueMap, eraseWhileIterating) {
runEraseWhileIterating<F14ValueMap<int, int>>();
......@@ -1099,6 +851,10 @@ TEST(F14VectorMap, eraseWhileIterating) {
runEraseWhileIterating<F14VectorMap<int, int>>();
}
TEST(F14FastMap, eraseWhileIterating) {
runEraseWhileIterating<F14FastMap<int, int>>();
}
TEST(F14ValueMap, rehash) {
runRehash<F14ValueMap<std::string, std::string>>();
}
......@@ -1111,6 +867,27 @@ TEST(F14VectorMap, rehash) {
runRehash<F14VectorMap<std::string, std::string>>();
}
TEST(F14FastMap, rehash) {
runRehash<F14VectorMap<std::string, std::string>>();
}
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
template <typename T>
void runPrehash() {
T h;
EXPECT_EQ(h.size(), 0);
h.insert(std::make_pair(s("abc"), s("ABC")));
EXPECT_TRUE(h.find(s("def")) == h.end());
EXPECT_FALSE(h.find(s("abc")) == h.end());
auto t1 = h.prehash(s("def"));
F14HashToken t2;
t2 = h.prehash(s("abc"));
EXPECT_TRUE(h.find(t1, s("def")) == h.end());
EXPECT_FALSE(h.find(t2, s("abc")) == h.end());
}
TEST(F14ValueMap, prehash) {
runPrehash<F14ValueMap<std::string, std::string>>();
}
......@@ -1119,6 +896,15 @@ TEST(F14NodeMap, prehash) {
runPrehash<F14NodeMap<std::string, std::string>>();
}
TEST(F14VectorMap, prehash) {
runPrehash<F14VectorMap<std::string, std::string>>();
}
TEST(F14FastMap, prehash) {
runPrehash<F14FastMap<std::string, std::string>>();
}
#endif
TEST(F14ValueMap, random) {
runRandom<F14ValueMap<
uint64_t,
......@@ -1161,10 +947,10 @@ TEST(F14ValueMap, grow_stats) {
h[i]++;
}
// F14ValueMap just before rehash
F14TableStats::compute(h);
runSanityChecks(h);
h[0]++;
// F14ValueMap just after rehash
F14TableStats::compute(h);
runSanityChecks(h);
}
TEST(F14ValueMap, steady_state_stats) {
......@@ -1182,15 +968,17 @@ TEST(F14ValueMap, steady_state_stats) {
h.erase(key);
}
if (((i + 1) % 10000) == 0) {
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
auto stats = F14TableStats::compute(h);
// Verify that average miss probe length is bounded despite continued
// erase + reuse. p99 of the average across 10M random steps is 4.69,
// average is 2.96.
EXPECT_LT(f14::expectedProbe(stats.missProbeLengthHisto), 10.0);
#endif
}
}
// F14ValueMap at steady state
F14TableStats::compute(h);
runSanityChecks(h);
}
TEST(F14VectorMap, steady_state_stats) {
......@@ -1208,15 +996,17 @@ TEST(F14VectorMap, steady_state_stats) {
h.erase(key);
}
if (((i + 1) % 10000) == 0) {
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
auto stats = F14TableStats::compute(h);
// Verify that average miss probe length is bounded despite continued
// erase + reuse. p99 of the average across 10M random steps is 4.69,
// average is 2.96.
EXPECT_LT(f14::expectedProbe(stats.missProbeLengthHisto), 10.0);
#endif
}
}
// F14ValueMap at steady state
F14TableStats::compute(h);
runSanityChecks(h);
}
TEST(Tracked, baseline) {
......@@ -1401,7 +1191,7 @@ void runInsertCases(
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
if (!kFallback) {
typename M::value_type p{0, 0};
M m;
m[0] = 0;
......@@ -1413,7 +1203,7 @@ void runInsertCases(
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
if (!kFallback) {
typename M::value_type p{0, 0};
M m;
m[0] = 0;
......@@ -1425,7 +1215,7 @@ void runInsertCases(
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
if (!kFallback) {
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
M m;
m[0] = 0;
......@@ -1437,7 +1227,7 @@ void runInsertCases(
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
if (!kFallback) {
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
M m;
m[0] = 0;
......@@ -1449,7 +1239,7 @@ void runInsertCases(
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
if (!kFallback) {
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
M m;
m[0] = 0;
......@@ -1467,7 +1257,7 @@ void runInsertCases(
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist * 2);
}
{
if (!kFallback) {
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
M m;
m[0] = 0;
......@@ -1588,6 +1378,8 @@ TEST(F14VectorMap, destructuring) {
}
TEST(F14VectorMap, destructuringErase) {
SKIP_IF(kFallback);
using M = F14VectorMap<Tracked<0>, Tracked<1>>;
typename M::value_type p1{0, 0};
typename M::value_type p2{2, 2};
......@@ -1608,6 +1400,8 @@ TEST(F14VectorMap, destructuringErase) {
}
TEST(F14ValueMap, maxSize) {
SKIP_IF(kFallback);
F14ValueMap<int, int> m;
EXPECT_EQ(
m.max_size(),
......@@ -1616,6 +1410,8 @@ TEST(F14ValueMap, maxSize) {
}
TEST(F14NodeMap, maxSize) {
SKIP_IF(kFallback);
F14NodeMap<int, int> m;
EXPECT_EQ(
m.max_size(),
......@@ -1624,6 +1420,8 @@ TEST(F14NodeMap, maxSize) {
}
TEST(F14VectorMap, vectorMaxSize) {
SKIP_IF(kFallback);
F14VectorMap<int, int> m;
EXPECT_EQ(
m.max_size(),
......@@ -1648,33 +1446,27 @@ void runMoveOnlyTest() {
}
TEST(F14ValueMap, moveOnly) {
runMoveOnlyTest<F14ValueMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14ValueMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueMap<
folly::test::MoveOnlyTestInt,
folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueMap<MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14ValueMap<int, MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
TEST(F14NodeMap, moveOnly) {
runMoveOnlyTest<F14NodeMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14NodeMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<
F14NodeMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14NodeMap<MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14NodeMap<int, MoveOnlyTestInt>>();
runMoveOnlyTest<F14NodeMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
TEST(F14VectorMap, moveOnly) {
runMoveOnlyTest<F14VectorMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14VectorMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorMap<
folly::test::MoveOnlyTestInt,
folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorMap<MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14VectorMap<int, MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
TEST(F14FastMap, moveOnly) {
runMoveOnlyTest<F14FastMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14FastMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<
F14FastMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14FastMap<MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14FastMap<int, MoveOnlyTestInt>>();
runMoveOnlyTest<F14FastMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
#if FOLLY_F14_ERASE_INTO_AVAILABLE
......@@ -1723,25 +1515,19 @@ void runEraseIntoTest() {
}
TEST(F14ValueMap, eraseInto) {
runEraseIntoTest<F14ValueMap<
folly::test::MoveOnlyTestInt,
folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14ValueMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
TEST(F14NodeMap, eraseInto) {
runEraseIntoTest<
F14NodeMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14NodeMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
TEST(F14VectorMap, eraseInto) {
runEraseIntoTest<F14VectorMap<
folly::test::MoveOnlyTestInt,
folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14VectorMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
TEST(F14FastMap, eraseInto) {
runEraseIntoTest<
F14FastMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14FastMap<MoveOnlyTestInt, MoveOnlyTestInt>>();
}
#endif
......@@ -1771,26 +1557,24 @@ void runPermissiveConstructorTest() {
TEST(F14ValueMap, permissiveConstructor) {
runPermissiveConstructorTest<F14ValueMap<
folly::test::PermissiveConstructorTestInt,
folly::test::PermissiveConstructorTestInt>>();
PermissiveConstructorTestInt,
PermissiveConstructorTestInt>>();
}
TEST(F14NodeMap, permissiveConstructor) {
runPermissiveConstructorTest<F14NodeMap<
folly::test::PermissiveConstructorTestInt,
folly::test::PermissiveConstructorTestInt>>();
runPermissiveConstructorTest<
F14NodeMap<PermissiveConstructorTestInt, PermissiveConstructorTestInt>>();
}
TEST(F14VectorMap, permissiveConstructor) {
runPermissiveConstructorTest<F14VectorMap<
folly::test::PermissiveConstructorTestInt,
folly::test::PermissiveConstructorTestInt>>();
PermissiveConstructorTestInt,
PermissiveConstructorTestInt>>();
}
TEST(F14FastMap, permissiveConstructor) {
runPermissiveConstructorTest<F14FastMap<
folly::test::PermissiveConstructorTestInt,
folly::test::PermissiveConstructorTestInt>>();
runPermissiveConstructorTest<
F14FastMap<PermissiveConstructorTestInt, PermissiveConstructorTestInt>>();
}
TEST(F14ValueMap, heterogeneousLookup) {
......@@ -1814,20 +1598,24 @@ TEST(F14ValueMap, heterogeneousLookup) {
EXPECT_TRUE(ref.end() == ref.find(buddy));
EXPECT_EQ(hello, ref.find(hello)->first);
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
const auto buddyHashToken = ref.prehash(buddy);
const auto helloHashToken = ref.prehash(hello);
// prehash + find
EXPECT_TRUE(ref.end() == ref.find(buddyHashToken, buddy));
EXPECT_EQ(hello, ref.find(helloHashToken, hello)->first);
#endif
// contains
EXPECT_FALSE(ref.contains(buddy));
EXPECT_TRUE(ref.contains(hello));
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
// contains with prehash
EXPECT_FALSE(ref.contains(buddyHashToken, buddy));
EXPECT_TRUE(ref.contains(helloHashToken, hello));
#endif
// equal_range
EXPECT_TRUE(std::make_pair(ref.end(), ref.end()) == ref.equal_range(buddy));
......@@ -1918,6 +1706,245 @@ TEST(F14FastMap, statefulFunctors) {
GenericAlloc<std::pair<int const, int>>>>();
}
template <typename M>
void runHeterogeneousInsertTest() {
M map;
resetTracking();
EXPECT_EQ(map.count(10), 0);
EXPECT_FALSE(map.contains(10));
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
resetTracking();
map[10] = 20;
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 1}), 0)
<< Tracked<1>::counts();
resetTracking();
std::pair<int, int> p(10, 30);
std::vector<std::pair<int, int>> v({p});
map[10] = 30;
map.insert(std::pair<int, int>(10, 30));
map.insert(std::pair<int const, int>(10, 30));
map.insert(p);
map.insert(v.begin(), v.end());
map.insert(
std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
map.insert_or_assign(10, 40);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
resetTracking();
map.emplace(10, 30);
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(10),
std::forward_as_tuple(30));
map.emplace(p);
map.try_emplace(10, 30);
map.try_emplace(10);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
resetTracking();
map.erase(10);
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
map.emplace(10, 40);
resetTracking();
map.erase(map.find(10));
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
#if FOLLY_F14_ERASE_INTO_AVAILABLE
map.emplace(10, 40);
resetTracking();
map.eraseInto(10, [](auto&&, auto&&) {});
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
#endif
}
template <typename M>
void runHeterogeneousInsertStringTest() {
using P = std::pair<StringPiece, std::string>;
using CP = std::pair<const StringPiece, std::string>;
M map;
P p{"foo", "hello"};
std::vector<P> v{p};
StringPiece foo{"foo"};
map.insert(P("foo", "hello"));
// TODO(T31574848): the list-initialization below does not work on libstdc++
// versions (e.g., GCC < 6) with no implementation of N4387 ("perfect
// initialization" for pairs and tuples).
// StringPiece sp{"foo"};
// map.insert({sp, "hello"});
map.insert({"foo", "hello"});
map.insert(CP("foo", "hello"));
map.insert(p);
map.insert(v.begin(), v.end());
map.insert(
std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
map.insert_or_assign("foo", "hello");
map.insert_or_assign(StringPiece{"foo"}, "hello");
EXPECT_EQ(map["foo"], "hello");
map.emplace(StringPiece{"foo"}, "hello");
map.emplace("foo", "hello");
map.emplace(p);
map.emplace();
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(StringPiece{"foo"}),
std::forward_as_tuple(/* count */ 20, 'x'));
map.try_emplace(StringPiece{"foo"}, "hello");
map.try_emplace(foo, "hello");
map.try_emplace(foo);
map.try_emplace("foo");
map.try_emplace("foo", "hello");
map.try_emplace("foo", /* count */ 20, 'x');
map.erase(StringPiece{"foo"});
map.erase(foo);
map.erase("");
EXPECT_TRUE(map.empty());
map.try_emplace(foo);
map.erase(map.find(foo));
map.try_emplace(foo);
typename M::const_iterator it = map.find(foo);
map.erase(it);
}
TEST(F14ValueMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14ValueMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14ValueMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14ValueMap<std::string, std::string>>();
}
TEST(F14NodeMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14NodeMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14NodeMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14NodeMap<std::string, std::string>>();
}
TEST(F14VectorMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14VectorMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14VectorMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14VectorMap<std::string, std::string>>();
}
TEST(F14FastMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14FastMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14FastMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14FastMap<std::string, std::string>>();
}
namespace {
// std::is_convertible is not transitive :( Problem scenario: B<T> is
// implicitly convertible to A, so hasher that takes A can be used as a
// transparent hasher for a map with key of type B<T>. C is implicitly
// convertible to any B<T>, but we have to disable heterogeneous find
// for C. There is no way to infer the T of the intermediate type so C
// can't be used to explicitly construct A.
struct A {
int value;
bool operator==(A const& rhs) const {
return value == rhs.value;
}
bool operator!=(A const& rhs) const {
return !(*this == rhs);
}
};
struct AHasher {
std::size_t operator()(A const& v) const {
return v.value;
}
};
template <typename T>
struct B {
int value;
explicit B(int v) : value(v) {}
/* implicit */ B(A const& v) : value(v.value) {}
/* implicit */ operator A() const {
return A{value};
}
};
struct C {
int value;
template <typename T>
/* implicit */ operator B<T>() const {
return B<T>{value};
}
};
} // namespace
TEST(F14FastMap, disabledDoubleTransparent) {
static_assert(std::is_convertible<B<char>, A>::value, "");
static_assert(std::is_convertible<C, B<char>>::value, "");
static_assert(!std::is_convertible<C, A>::value, "");
F14FastMap<
B<char>,
int,
folly::transparent<AHasher>,
folly::transparent<std::equal_to<A>>>
map;
map[A{10}] = 10;
EXPECT_TRUE(map.find(C{10}) != map.end());
EXPECT_TRUE(map.find(C{20}) == map.end());
}
template <typename M>
void runRandomInsertOrderTest() {
if (FOLLY_F14_PERTURB_INSERTION_ORDER) {
......@@ -1960,6 +1987,8 @@ TEST(F14Map, randomInsertOrder) {
template <typename M>
void runContinuousCapacityTest(std::size_t minSize, std::size_t maxSize) {
SKIP_IF(kFallback);
using K = typename M::key_type;
for (std::size_t n = minSize; n <= maxSize; ++n) {
M m1;
......@@ -2007,48 +2036,46 @@ void runContinuousCapacityTest(std::size_t minSize, std::size_t maxSize) {
}
TEST(F14Map, continuousCapacitySmall0) {
runContinuousCapacityTest<folly::F14NodeMap<std::size_t, std::string>>(1, 14);
runContinuousCapacityTest<F14NodeMap<std::size_t, std::string>>(1, 14);
}
TEST(F14Map, continuousCapacitySmall1) {
runContinuousCapacityTest<folly::F14ValueMap<std::size_t, std::string>>(
1, 14);
runContinuousCapacityTest<F14ValueMap<std::size_t, std::string>>(1, 14);
}
TEST(F14Map, continuousCapacitySmall2) {
runContinuousCapacityTest<folly::F14VectorMap<std::size_t, std::string>>(
1, 100);
runContinuousCapacityTest<F14VectorMap<std::size_t, std::string>>(1, 100);
}
TEST(F14Map, continuousCapacitySmall3) {
runContinuousCapacityTest<folly::F14FastMap<std::size_t, std::string>>(1, 14);
runContinuousCapacityTest<F14FastMap<std::size_t, std::string>>(1, 14);
}
TEST(F14Map, continuousCapacityBig0) {
runContinuousCapacityTest<folly::F14VectorMap<std::size_t, std::string>>(
runContinuousCapacityTest<F14VectorMap<std::size_t, std::string>>(
1000000 - 1, 1000000 - 1);
}
TEST(F14Map, continuousCapacityBig1) {
runContinuousCapacityTest<folly::F14VectorMap<std::size_t, std::string>>(
runContinuousCapacityTest<F14VectorMap<std::size_t, std::string>>(
1000000, 1000000);
}
TEST(F14Map, continuousCapacityBig2) {
runContinuousCapacityTest<folly::F14VectorMap<std::size_t, std::string>>(
runContinuousCapacityTest<F14VectorMap<std::size_t, std::string>>(
1000000 + 1, 1000000 + 1);
}
TEST(F14Map, continuousCapacityBig3) {
runContinuousCapacityTest<folly::F14VectorMap<std::size_t, std::string>>(
runContinuousCapacityTest<F14VectorMap<std::size_t, std::string>>(
1000000 + 2, 1000000 + 2);
}
TEST(F14Map, continuousCapacityF12) {
runContinuousCapacityTest<folly::F14VectorMap<uint16_t, uint16_t>>(
0xfff0, 0xfffe);
runContinuousCapacityTest<F14VectorMap<uint16_t, uint16_t>>(0xfff0, 0xfffe);
}
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
template <template <class...> class TMap>
void testContainsWithPrecomputedHash() {
TMap<int, int> m{};
......@@ -2067,6 +2094,7 @@ TEST(F14Map, containsWithPrecomputedHash) {
testContainsWithPrecomputedHash<F14NodeMap>();
testContainsWithPrecomputedHash<F14FastMap>();
}
#endif
template <template <class...> class TMap>
void testEraseIf() {
......@@ -2126,7 +2154,3 @@ TEST(F14Map, copyAfterRemovedCollisions) {
testCopyAfterRemovedCollisions<F14NodeMap>();
testCopyAfterRemovedCollisions<F14FastMap>();
}
///////////////////////////////////
#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
......@@ -22,7 +22,11 @@ FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
#include <folly/container/F14Set.h>
#include <chrono>
#include <random>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <glog/logging.h>
......@@ -32,30 +36,41 @@ FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
#include <folly/container/test/F14TestUtil.h>
#include <folly/container/test/TrackingTypes.h>
#include <folly/portability/GTest.h>
#include <folly/test/TestUtils.h>
using namespace folly;
using namespace folly::f14;
using namespace folly::string_piece_literals;
using namespace folly::test;
static constexpr bool kFallback = folly::f14::detail::getF14IntrinsicsMode() ==
folly::f14::detail::F14IntrinsicsMode::None;
template <typename T>
void runSanityChecks(T const& t) {
(void)t;
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
F14TableStats::compute(t);
#endif
}
template <template <typename, typename, typename, typename> class TSet>
void testCustomSwap() {
using std::swap;
TSet<
int,
folly::f14::DefaultHasher<int>,
folly::f14::DefaultKeyEqual<int>,
folly::test::SwapTrackingAlloc<int>>
TSet<int, DefaultHasher<int>, DefaultKeyEqual<int>, SwapTrackingAlloc<int>>
m0, m1;
folly::test::resetTracking();
resetTracking();
swap(m0, m1);
EXPECT_EQ(
0,
folly::test::Tracked<0>::counts().dist(folly::test::Counts{0, 0, 0, 0}));
EXPECT_EQ(0, Tracked<0>::counts().dist(Counts{0, 0, 0, 0}));
}
TEST(F14Set, customSwap) {
testCustomSwap<folly::F14ValueSet>();
testCustomSwap<folly::F14NodeSet>();
testCustomSwap<folly::F14VectorSet>();
testCustomSwap<folly::F14FastSet>();
testCustomSwap<F14ValueSet>();
testCustomSwap<F14NodeSet>();
testCustomSwap<F14VectorSet>();
testCustomSwap<F14FastSet>();
}
namespace {
......@@ -63,9 +78,6 @@ template <
template <typename, typename, typename, typename> class TSet,
typename K>
void runAllocatedMemorySizeTest() {
using namespace folly::f14;
using namespace folly::f14::detail;
using namespace folly::test;
using A = SwapTrackingAlloc<K>;
resetTracking();
......@@ -75,9 +87,7 @@ void runAllocatedMemorySizeTest() {
// if F14 intrinsics are not available then we fall back to using
// std::unordered_set underneath, but in that case the allocation
// info is only best effort
bool preciseAllocInfo = getF14IntrinsicsMode() != F14IntrinsicsMode::None;
if (preciseAllocInfo) {
if (!kFallback) {
EXPECT_EQ(testAllocatedMemorySize(), 0);
EXPECT_EQ(s.getAllocatedMemorySize(), 0);
}
......@@ -85,9 +95,9 @@ void runAllocatedMemorySizeTest() {
auto emptySetAllocatedBlockCount = testAllocatedBlockCount();
for (size_t i = 0; i < 1000; ++i) {
s.insert(folly::to<K>(i));
s.erase(folly::to<K>(i / 10 + 2));
if (preciseAllocInfo) {
s.insert(to<K>(i));
s.erase(to<K>(i / 10 + 2));
if (!kFallback) {
EXPECT_EQ(testAllocatedMemorySize(), s.getAllocatedMemorySize());
}
EXPECT_GE(s.getAllocatedMemorySize(), sizeof(K) * s.size());
......@@ -98,7 +108,7 @@ void runAllocatedMemorySizeTest() {
size += bytes * n;
count += n;
});
if (preciseAllocInfo) {
if (!kFallback) {
EXPECT_EQ(testAllocatedMemorySize(), size);
EXPECT_EQ(testAllocatedBlockCount(), count);
}
......@@ -111,7 +121,7 @@ void runAllocatedMemorySizeTest() {
s.reserve(5);
EXPECT_GT(testAllocatedMemorySize(), 0);
s = {};
if (preciseAllocInfo) {
if (!kFallback) {
EXPECT_GT(testAllocatedMemorySize(), 0);
}
}
......@@ -121,10 +131,10 @@ void runAllocatedMemorySizeTest() {
template <typename K>
void runAllocatedMemorySizeTests() {
runAllocatedMemorySizeTest<folly::F14ValueSet, K>();
runAllocatedMemorySizeTest<folly::F14NodeSet, K>();
runAllocatedMemorySizeTest<folly::F14VectorSet, K>();
runAllocatedMemorySizeTest<folly::F14FastSet, K>();
runAllocatedMemorySizeTest<F14ValueSet, K>();
runAllocatedMemorySizeTest<F14NodeSet, K>();
runAllocatedMemorySizeTest<F14VectorSet, K>();
runAllocatedMemorySizeTest<F14FastSet, K>();
}
} // namespace
......@@ -134,7 +144,7 @@ TEST(F14Set, getAllocatedMemorySize) {
runAllocatedMemorySizeTests<long>();
runAllocatedMemorySizeTests<double>();
runAllocatedMemorySizeTests<std::string>();
runAllocatedMemorySizeTests<folly::fbstring>();
runAllocatedMemorySizeTests<fbstring>();
}
template <typename S>
......@@ -142,7 +152,7 @@ void runVisitContiguousRangesTest(int n) {
S set;
for (int i = 0; i < n; ++i) {
folly::makeUnpredictable(i);
makeUnpredictable(i);
set.insert(i);
set.erase(i / 2);
}
......@@ -175,27 +185,27 @@ void runVisitContiguousRangesTest() {
}
TEST(F14ValueSet, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14ValueSet<int>>();
runVisitContiguousRangesTest<F14ValueSet<int>>();
}
TEST(F14NodeSet, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14NodeSet<int>>();
runVisitContiguousRangesTest<F14NodeSet<int>>();
}
TEST(F14VectorSet, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14VectorSet<int>>();
runVisitContiguousRangesTest<F14VectorSet<int>>();
}
TEST(F14FastSet, visitContiguousRanges) {
runVisitContiguousRangesTest<folly::F14FastSet<int>>();
runVisitContiguousRangesTest<F14FastSet<int>>();
}
#if FOLLY_HAS_MEMORY_RESOURCE
TEST(F14Set, pmr_empty) {
folly::pmr::F14ValueSet<int> s1;
folly::pmr::F14NodeSet<int> s2;
folly::pmr::F14VectorSet<int> s3;
folly::pmr::F14FastSet<int> s4;
pmr::F14ValueSet<int> s1;
pmr::F14NodeSet<int> s2;
pmr::F14VectorSet<int> s3;
pmr::F14FastSet<int> s4;
EXPECT_TRUE(s1.empty() && s2.empty() && s3.empty() && s4.empty());
}
#endif
......@@ -224,7 +234,7 @@ std::size_t NestedHash::operator()(N const& v) const {
for (auto& k : *v.set_) {
rv += operator()(k);
}
return folly::Hash{}(rv);
return Hash{}(rv);
}
template <template <class...> class TSet>
......@@ -252,8 +262,8 @@ void testNestedSetEquality() {
template <template <class...> class TSet>
void testEqualityRefinement() {
TSet<std::pair<int, int>, folly::test::HashFirst, folly::test::EqualFirst> s1;
TSet<std::pair<int, int>, folly::test::HashFirst, folly::test::EqualFirst> s2;
TSet<std::pair<int, int>, HashFirst, EqualFirst> s1;
TSet<std::pair<int, int>, HashFirst, EqualFirst> s2;
s1.insert(std::make_pair(0, 0));
s1.insert(std::make_pair(1, 1));
EXPECT_FALSE(s1.insert(std::make_pair(0, 2)).second);
......@@ -272,33 +282,19 @@ void testEqualityRefinement() {
} // namespace
TEST(F14Set, nestedSetEquality) {
testNestedSetEquality<folly::F14ValueSet>();
testNestedSetEquality<folly::F14NodeSet>();
testNestedSetEquality<folly::F14VectorSet>();
testNestedSetEquality<folly::F14FastSet>();
testNestedSetEquality<F14ValueSet>();
testNestedSetEquality<F14NodeSet>();
testNestedSetEquality<F14VectorSet>();
testNestedSetEquality<F14FastSet>();
}
TEST(F14Set, equalityRefinement) {
testEqualityRefinement<folly::F14ValueSet>();
testEqualityRefinement<folly::F14NodeSet>();
testEqualityRefinement<folly::F14VectorSet>();
testEqualityRefinement<folly::F14FastSet>();
testEqualityRefinement<F14ValueSet>();
testEqualityRefinement<F14NodeSet>();
testEqualityRefinement<F14VectorSet>();
testEqualityRefinement<F14FastSet>();
}
///////////////////////////////////
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
#include <chrono>
#include <random>
#include <string>
#include <unordered_set>
using namespace folly;
using namespace folly::f14;
using namespace folly::string_piece_literals;
using namespace folly::test;
namespace {
std::string s(char const* p) {
return p;
......@@ -314,11 +310,15 @@ void runSimple() {
std::vector<std::string> v({"abc", "abc"});
h.insert(v.begin(), v.begin());
EXPECT_EQ(h.size(), 0);
if (!kFallback) {
EXPECT_EQ(h.bucket_count(), 0);
}
h.insert(v.begin(), v.end());
EXPECT_EQ(h.size(), 1);
h = T{};
if (!kFallback) {
EXPECT_EQ(h.bucket_count(), 0);
}
h.insert(s("abc"));
EXPECT_TRUE(h.find(s("def")) == h.end());
......@@ -417,14 +417,14 @@ void runSimple() {
expectH8((h8 = std::move(h2)));
expectH8((h8 = {}));
F14TableStats::compute(h);
F14TableStats::compute(h2);
F14TableStats::compute(h3);
F14TableStats::compute(h4);
F14TableStats::compute(h5);
F14TableStats::compute(h6);
F14TableStats::compute(h7);
F14TableStats::compute(h8);
runSanityChecks(h);
runSanityChecks(h2);
runSanityChecks(h3);
runSanityChecks(h4);
runSanityChecks(h5);
runSanityChecks(h6);
runSanityChecks(h7);
runSanityChecks(h8);
}
template <typename T>
......@@ -461,7 +461,7 @@ void runRehash() {
h.insert(to<std::string>(i));
}
EXPECT_EQ(h.size(), n);
F14TableStats::compute(h);
runSanityChecks(h);
}
// T should be a set of uint64_t
......@@ -639,7 +639,7 @@ void runRandom() {
EXPECT_EQ((t0 == t1), (r0 == r1));
} else if (pct < 99) {
// clear
t0.computeStats();
runSanityChecks(t0);
t0.clear();
r0.clear();
} else if (pct < 100) {
......@@ -694,8 +694,10 @@ TEST(F14FastSet, pmr_simple) {
#endif
TEST(F14Set, ContainerSize) {
SKIP_IF(kFallback);
{
folly::F14ValueSet<int> set;
F14ValueSet<int> set;
set.insert(10);
EXPECT_EQ(sizeof(set), 4 * sizeof(void*));
if (alignof(folly::max_align_t) == 16) {
......@@ -707,7 +709,7 @@ TEST(F14Set, ContainerSize) {
}
}
{
folly::F14NodeSet<int> set;
F14NodeSet<int> set;
set.insert(10);
EXPECT_EQ(sizeof(set), 4 * sizeof(void*));
if (alignof(folly::max_align_t) == 16) {
......@@ -719,13 +721,14 @@ TEST(F14Set, ContainerSize) {
}
}
{
folly::F14VectorSet<int> set;
F14VectorSet<int> set;
set.insert(10);
EXPECT_EQ(sizeof(set), 8 + 2 * sizeof(void*));
EXPECT_EQ(set.getAllocatedMemorySize(), 32);
}
}
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
TEST(F14VectorMap, reverse_iterator) {
using TSet = F14VectorSet<uint64_t>;
auto populate = [](TSet& h, uint64_t lo, uint64_t hi) {
......@@ -778,6 +781,7 @@ TEST(F14VectorSet, OrderPreservingReinsertionView) {
EXPECT_EQ(asVector(s1), asVector(s2));
}
#endif
TEST(F14ValueSet, eraseWhileIterating) {
runEraseWhileIterating<F14ValueSet<int>>();
......@@ -816,18 +820,22 @@ TEST(F14VectorSet, random) {
}
TEST(F14ValueSet, grow_stats) {
SKIP_IF(kFallback);
F14ValueSet<uint64_t> h;
for (unsigned i = 1; i <= 3072; ++i) {
h.insert(i);
}
// F14ValueSet just before rehash
F14TableStats::compute(h);
runSanityChecks(h);
h.insert(0);
// F14ValueSet just after rehash
F14TableStats::compute(h);
runSanityChecks(h);
}
TEST(F14ValueSet, steady_state_stats) {
SKIP_IF(kFallback);
// 10k keys, 14% probability of insert, 90% chance of erase, so the
// table should converge to 1400 size without triggering the rehash
// that would occur at 1536.
......@@ -842,15 +850,17 @@ TEST(F14ValueSet, steady_state_stats) {
h.erase(key);
}
if (((i + 1) % 10000) == 0) {
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
auto stats = F14TableStats::compute(h);
// Verify that average miss probe length is bounded despite continued
// erase + reuse. p99 of the average across 10M random steps is 4.69,
// average is 2.96.
EXPECT_LT(f14::expectedProbe(stats.missProbeLengthHisto), 10.0);
#endif
}
}
// F14ValueSet at steady state
F14TableStats::compute(h);
runSanityChecks(h);
}
// S should be a set of Tracked<0>. F should take a set
......@@ -930,23 +940,31 @@ void runInsertAndEmplace() {
resetTracking();
EXPECT_TRUE(s.emplace(k1).second);
// copy is expected on successful emplace
EXPECT_EQ(Tracked<0>::counts().dist(Counts{1, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{1, 0, 0, 0}), 0)
<< Tracked<0>::counts();
resetTracking();
EXPECT_FALSE(s.emplace(k2).second);
if (!kFallback) {
// no copies or moves on failing emplace with value_type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<0>::counts();
}
resetTracking();
EXPECT_FALSE(s.emplace(k3).second);
if (!kFallback) {
// copy convert expected for failing emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 1, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 1, 0}), 0)
<< Tracked<0>::counts();
}
s.clear();
resetTracking();
EXPECT_TRUE(s.emplace(k3).second);
// copy convert + move expected for successful emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 1, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 1, 0}), 0)
<< Tracked<0>::counts();
}
{
typename S::value_type k1{0};
......@@ -956,23 +974,31 @@ void runInsertAndEmplace() {
resetTracking();
EXPECT_TRUE(s.emplace(std::move(k1)).second);
// move is expected on successful emplace
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 0}), 0)
<< Tracked<0>::counts();
resetTracking();
EXPECT_FALSE(s.emplace(std::move(k2)).second);
if (!kFallback) {
// no copies or moves on failing emplace with value_type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<0>::counts();
}
resetTracking();
EXPECT_FALSE(s.emplace(std::move(k3)).second);
if (!kFallback) {
// move convert expected for failing emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 1}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 1}), 0)
<< Tracked<0>::counts();
}
s.clear();
resetTracking();
EXPECT_TRUE(s.emplace(std::move(k3)).second);
// move convert + move expected for successful emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 1}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 1}), 0)
<< Tracked<0>::counts();
}
// Calling the default pair constructor via emplace is valid, but not
......@@ -1002,6 +1028,8 @@ TEST(F14VectorSet, destructuring) {
}
TEST(F14ValueSet, maxSize) {
SKIP_IF(kFallback);
F14ValueSet<int> s;
EXPECT_EQ(
s.max_size(),
......@@ -1010,6 +1038,8 @@ TEST(F14ValueSet, maxSize) {
}
TEST(F14NodeSet, maxSize) {
SKIP_IF(kFallback);
F14NodeSet<int> s;
EXPECT_EQ(
s.max_size(),
......@@ -1018,6 +1048,8 @@ TEST(F14NodeSet, maxSize) {
}
TEST(F14VectorSet, maxSize) {
SKIP_IF(kFallback);
F14VectorSet<int> s;
EXPECT_EQ(
s.max_size(),
......@@ -1041,21 +1073,22 @@ void runMoveOnlyTest() {
}
TEST(F14ValueSet, moveOnly) {
runMoveOnlyTest<F14ValueSet<folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueSet<MoveOnlyTestInt>>();
}
TEST(F14NodeSet, moveOnly) {
runMoveOnlyTest<F14NodeSet<folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14NodeSet<MoveOnlyTestInt>>();
}
TEST(F14VectorSet, moveOnly) {
runMoveOnlyTest<F14VectorSet<folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorSet<MoveOnlyTestInt>>();
}
TEST(F14FastSet, moveOnly) {
runMoveOnlyTest<F14FastSet<folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14FastSet<MoveOnlyTestInt>>();
}
#if FOLLY_F14_ERASE_INTO_AVAILABLE
template <typename S>
void runEraseIntoTest() {
S t0;
......@@ -1077,6 +1110,10 @@ void runEraseIntoTest() {
EXPECT_TRUE(t0.find(10) != t0.end());
EXPECT_TRUE(t0.find(20) != t0.end());
t1.insert(20);
t1.eraseInto(t1.cbegin(), insertIntoT0);
EXPECT_TRUE(t1.empty());
t1.insert(20);
t1.insert(30);
t1.insert(40);
......@@ -1100,25 +1137,26 @@ void runEraseIntoTest() {
}
TEST(F14ValueSet, eraseInto) {
runEraseIntoTest<F14ValueSet<folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14ValueSet<MoveOnlyTestInt>>();
}
TEST(F14NodeSet, eraseInto) {
runEraseIntoTest<F14NodeSet<folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14NodeSet<MoveOnlyTestInt>>();
}
TEST(F14VectorSet, eraseInto) {
runEraseIntoTest<F14VectorSet<folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14VectorSet<MoveOnlyTestInt>>();
}
TEST(F14FastSet, eraseInto) {
runEraseIntoTest<F14FastSet<folly::test::MoveOnlyTestInt>>();
runEraseIntoTest<F14FastSet<MoveOnlyTestInt>>();
}
#endif
TEST(F14ValueSet, heterogeneous) {
// note: std::string is implicitly convertible to but not from StringPiece
using Hasher = folly::transparent<folly::hasher<folly::StringPiece>>;
using KeyEqual = folly::transparent<std::equal_to<folly::StringPiece>>;
using Hasher = transparent<hasher<StringPiece>>;
using KeyEqual = transparent<std::equal_to<StringPiece>>;
constexpr auto hello = "hello"_sp;
constexpr auto buddy = "buddy"_sp;
......@@ -1137,20 +1175,24 @@ TEST(F14ValueSet, heterogeneous) {
EXPECT_TRUE(ref.end() == ref.find(buddy));
EXPECT_EQ(hello, *ref.find(hello));
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
const auto buddyHashToken = ref.prehash(buddy);
const auto helloHashToken = ref.prehash(hello);
// prehash + find
EXPECT_TRUE(ref.end() == ref.find(buddyHashToken, buddy));
EXPECT_EQ(hello, *ref.find(helloHashToken, hello));
#endif
// contains
EXPECT_FALSE(ref.contains(buddy));
EXPECT_TRUE(ref.contains(hello));
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
// contains with prehash
EXPECT_FALSE(ref.contains(buddyHashToken, buddy));
EXPECT_TRUE(ref.contains(helloHashToken, hello));
#endif
// equal_range
EXPECT_TRUE(std::make_pair(ref.end(), ref.end()) == ref.equal_range(buddy));
......@@ -1160,7 +1202,7 @@ TEST(F14ValueSet, heterogeneous) {
};
checks(set);
checks(folly::as_const(set));
checks(as_const(set));
}
template <typename S>
......@@ -1277,11 +1319,20 @@ void runHeterogeneousInsertTest() {
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
set.insert(10);
resetTracking();
set.erase(set.find(10));
EXPECT_EQ(set.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
#if FOLLY_F14_ERASE_INTO_AVAILABLE
set.insert(10);
resetTracking();
set.eraseInto(10, [](auto&&) {});
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
#endif
}
template <typename S>
......@@ -1306,6 +1357,12 @@ void runHeterogeneousInsertStringTest() {
set.erase(k);
set.erase(StringPiece{"foo"});
EXPECT_TRUE(set.empty());
set.insert(k);
set.erase(set.find(k));
set.insert(k);
typename S::const_iterator it = set.find(k);
set.erase(it);
}
TEST(F14ValueSet, heterogeneousInsert) {
......@@ -1410,11 +1467,7 @@ TEST(F14FastSet, disabledDoubleTransparent) {
static_assert(std::is_convertible<C, B<char>>::value, "");
static_assert(!std::is_convertible<C, A>::value, "");
F14FastSet<
B<char>,
folly::transparent<AHasher>,
folly::transparent<std::equal_to<A>>>
set;
F14FastSet<B<char>, transparent<AHasher>, transparent<std::equal_to<A>>> set;
set.emplace(A{10});
EXPECT_TRUE(set.find(C{10}) != set.end());
......@@ -1425,8 +1478,7 @@ namespace {
struct CharArrayHasher {
template <std::size_t N>
std::size_t operator()(std::array<char, N> const& value) const {
return folly::Hash{}(
StringPiece{value.data(), &value.data()[value.size()]});
return Hash{}(StringPiece{value.data(), &value.data()[value.size()]});
}
};
......@@ -1497,6 +1549,7 @@ TEST(F14Set, randomInsertOrder) {
});
}
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
template <template <class...> class TSet>
void testContainsWithPrecomputedHash() {
TSet<int> m{};
......@@ -1515,6 +1568,7 @@ TEST(F14Set, containsWithPrecomputedHash) {
testContainsWithPrecomputedHash<F14VectorSet>();
testContainsWithPrecomputedHash<F14FastSet>();
}
#endif
template <template <class...> class TSet>
void testEraseIf() {
......@@ -1547,7 +1601,3 @@ TEST(F14Set, ExceptionOnInsert) {
testExceptionOnInsert<F14VectorSet>();
testExceptionOnInsert<F14FastSet>();
}
///////////////////////////////////
#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
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