Commit 7ae9308b authored by Nathan Bronson's avatar Nathan Bronson Committed by Facebook Github Bot

F14 fallback mode cleanups

Summary:
This diff adds operator=(initializer_list) to the F14 fallback
implementations, relaxes the guarantees provided by visitAllocationClasses
for fallbacks (so that the current implementation can be considered
correct), enables additional tests, and avoids a bit of logic repetition.
Changes should only be made to the fallback mode and tests, and this diff
should not change the set of platforms for which fallback mode is enabled.

Reviewed By: shixiao

Differential Revision: D9664700

fbshipit-source-id: 1f3fc6f0c48009bb3e5fd249506b42f54d93df43
parent 5bb6b0ae
...@@ -54,31 +54,39 @@ class F14BasicMap : public std::unordered_map<K, M, H, E, A> { ...@@ -54,31 +54,39 @@ class F14BasicMap : public std::unordered_map<K, M, H, E, A> {
using Super = std::unordered_map<K, M, H, E, A>; using Super = std::unordered_map<K, M, H, E, A>;
public: public:
using typename Super::pointer;
using typename Super::value_type;
F14BasicMap() = default;
using Super::Super; using Super::Super;
F14BasicMap() : Super() {}
//// PUBLIC - F14 Extensions //// PUBLIC - F14 Extensions
typename Super::size_type getAllocatedMemorySize() const { // exact for libstdc++, approximate for others
auto bc = this->bucket_count(); std::size_t getAllocatedMemorySize() const {
return (bc == 1 ? 0 : bc) * sizeof(typename Super::pointer) + std::size_t rv = 0;
this->size() * sizeof(StdNodeReplica<K, typename Super::value_type, H>); visitAllocationClasses(
[&](std::size_t bytes, std::size_t n) { rv += bytes * n; });
return rv;
} }
// exact for libstdc++, approximate for others
template <typename V> template <typename V>
void visitAllocationClasses(V&& visitor) const { void visitAllocationClasses(V&& visitor) const {
auto bc = this->bucket_count(); auto bc = this->bucket_count();
if (bc > 1) { if (bc > 1) {
visitor(bc * sizeof(typename Super::pointer), 1); visitor(bc * sizeof(pointer), 1);
}
if (this->size() > 0) {
visitor(sizeof(StdNodeReplica<K, value_type, H>), this->size());
} }
visitor(
sizeof(StdNodeReplica<K, typename Super::value_type, H>), this->size());
} }
template <typename V> template <typename V>
void visitContiguousRanges(V&& visitor) const { void visitContiguousRanges(V&& visitor) const {
for (typename Super::value_type const& entry : *this) { for (value_type const& entry : *this) {
typename Super::value_type const* b = std::addressof(entry); value_type const* b = std::addressof(entry);
visitor(b, b + 1); visitor(b, b + 1);
} }
} }
...@@ -91,8 +99,16 @@ class F14ValueMap : public f14::detail::F14BasicMap<K, M, H, E, A> { ...@@ -91,8 +99,16 @@ class F14ValueMap : public f14::detail::F14BasicMap<K, M, H, E, A> {
using Super = f14::detail::F14BasicMap<K, M, H, E, A>; using Super = f14::detail::F14BasicMap<K, M, H, E, A>;
public: public:
using typename Super::value_type;
F14ValueMap() = default;
using Super::Super; using Super::Super;
F14ValueMap() : Super() {}
F14ValueMap& operator=(std::initializer_list<value_type> ilist) {
Super::operator=(ilist);
return *this;
}
}; };
template <typename K, typename M, typename H, typename E, typename A> template <typename K, typename M, typename H, typename E, typename A>
...@@ -100,8 +116,16 @@ class F14NodeMap : public f14::detail::F14BasicMap<K, M, H, E, A> { ...@@ -100,8 +116,16 @@ class F14NodeMap : public f14::detail::F14BasicMap<K, M, H, E, A> {
using Super = f14::detail::F14BasicMap<K, M, H, E, A>; using Super = f14::detail::F14BasicMap<K, M, H, E, A>;
public: public:
using typename Super::value_type;
F14NodeMap() = default;
using Super::Super; using Super::Super;
F14NodeMap() : Super() {}
F14NodeMap& operator=(std::initializer_list<value_type> ilist) {
Super::operator=(ilist);
return *this;
}
}; };
template <typename K, typename M, typename H, typename E, typename A> template <typename K, typename M, typename H, typename E, typename A>
...@@ -109,8 +133,16 @@ class F14VectorMap : public f14::detail::F14BasicMap<K, M, H, E, A> { ...@@ -109,8 +133,16 @@ class F14VectorMap : public f14::detail::F14BasicMap<K, M, H, E, A> {
using Super = f14::detail::F14BasicMap<K, M, H, E, A>; using Super = f14::detail::F14BasicMap<K, M, H, E, A>;
public: public:
using typename Super::value_type;
F14VectorMap() = default;
using Super::Super; using Super::Super;
F14VectorMap() : Super() {}
F14VectorMap& operator=(std::initializer_list<value_type> ilist) {
Super::operator=(ilist);
return *this;
}
}; };
} // namespace folly } // namespace folly
......
...@@ -50,33 +50,39 @@ class F14BasicSet : public std::unordered_set<K, H, E, A> { ...@@ -50,33 +50,39 @@ class F14BasicSet : public std::unordered_set<K, H, E, A> {
using Super = std::unordered_set<K, H, E, A>; using Super = std::unordered_set<K, H, E, A>;
public: public:
using typename Super::pointer;
using typename Super::value_type;
F14BasicSet() = default;
using Super::Super; using Super::Super;
F14BasicSet() : Super() {}
//// PUBLIC - F14 Extensions //// PUBLIC - F14 Extensions
typename Super::size_type getAllocatedMemorySize() const { // exact for libstdc++, approximate for others
auto bc = this->bucket_count(); std::size_t getAllocatedMemorySize() const {
return (bc == 1 ? 0 : bc) * sizeof(typename Super::pointer) + std::size_t rv = 0;
this->size() * sizeof(StdNodeReplica<K, typename Super::value_type, H>); visitAllocationClasses(
[&](std::size_t bytes, std::size_t n) { rv += bytes * n; });
return rv;
} }
// TODO(T33376370): this implementation is incorrect for android. Fix it or // exact for libstdc++, approximate for others
// disable it.
template <typename V> template <typename V>
void visitAllocationClasses(V&& visitor) const { void visitAllocationClasses(V&& visitor) const {
auto bc = this->bucket_count(); auto bc = this->bucket_count();
if (bc > 1) { if (bc > 1) {
visitor(bc * sizeof(typename Super::pointer), 1); visitor(bc * sizeof(pointer), 1);
}
if (this->size() > 0) {
visitor(sizeof(StdNodeReplica<K, value_type, H>), this->size());
} }
visitor(
sizeof(StdNodeReplica<K, typename Super::value_type, H>), this->size());
} }
template <typename V> template <typename V>
void visitContiguousRanges(V&& visitor) const { void visitContiguousRanges(V&& visitor) const {
for (typename Super::value_type const& entry : *this) { for (value_type const& entry : *this) {
typename Super::value_type const* b = std::addressof(entry); value_type const* b = std::addressof(entry);
visitor(b, b + 1); visitor(b, b + 1);
} }
} }
...@@ -89,8 +95,16 @@ class F14NodeSet : public f14::detail::F14BasicSet<K, H, E, A> { ...@@ -89,8 +95,16 @@ class F14NodeSet : public f14::detail::F14BasicSet<K, H, E, A> {
using Super = f14::detail::F14BasicSet<K, H, E, A>; using Super = f14::detail::F14BasicSet<K, H, E, A>;
public: public:
using typename Super::value_type;
F14NodeSet() = default;
using Super::Super; using Super::Super;
F14NodeSet() : Super() {}
F14NodeSet& operator=(std::initializer_list<value_type> ilist) {
Super::operator=(ilist);
return *this;
}
}; };
template <typename K, typename H, typename E, typename A> template <typename K, typename H, typename E, typename A>
...@@ -98,8 +112,16 @@ class F14ValueSet : public f14::detail::F14BasicSet<K, H, E, A> { ...@@ -98,8 +112,16 @@ class F14ValueSet : public f14::detail::F14BasicSet<K, H, E, A> {
using Super = f14::detail::F14BasicSet<K, H, E, A>; using Super = f14::detail::F14BasicSet<K, H, E, A>;
public: public:
using Super::Super; using typename Super::value_type;
F14ValueSet() : Super() {} F14ValueSet() : Super() {}
using Super::Super;
F14ValueSet& operator=(std::initializer_list<value_type> ilist) {
Super::operator=(ilist);
return *this;
}
}; };
template <typename K, typename H, typename E, typename A> template <typename K, typename H, typename E, typename A>
...@@ -107,8 +129,16 @@ class F14VectorSet : public f14::detail::F14BasicSet<K, H, E, A> { ...@@ -107,8 +129,16 @@ class F14VectorSet : public f14::detail::F14BasicSet<K, H, E, A> {
using Super = f14::detail::F14BasicSet<K, H, E, A>; using Super = f14::detail::F14BasicSet<K, H, E, A>;
public: public:
using typename Super::value_type;
F14VectorSet() = default;
using Super::Super; using Super::Super;
F14VectorSet() : Super() {}
F14VectorSet& operator=(std::initializer_list<value_type> ilist) {
Super::operator=(ilist);
return *this;
}
}; };
} // namespace folly } // namespace folly
......
...@@ -18,16 +18,21 @@ ...@@ -18,16 +18,21 @@
#include <folly/Portability.h> #include <folly/Portability.h>
// F14 has been implemented for SSE2 and NEON (so far). The NEON version is only // F14 has been implemented for SSE2 and NEON (so far).
// enabled on aarch64 as it is difficult to ensure that dependent targets on
// other Android ARM platforms set the NEON compilation flags consistently. If
// dependent targets don't consistently build with NEON, due to C++ templates
// and ODR, the NEON version may be linked in where a non-NEON version is
// expected.
// //
// TODO: enable for android (T33376370) // This platform detection is a bit of a mess because it combines the
// TODO: enable for iOS (T33470422) // detection of supported platforms (FOLLY_SSE >= 2 || FOLLY_NEON) with
#if ((FOLLY_SSE >= 2) || (FOLLY_NEON && FOLLY_AARCH64)) && (!FOLLY_MOBILE) // the selection of platforms on which we want to use it.
//
// Currently no 32-bit ARM versions are desired because we don't want to
// need a separate build for chips that don't have NEON. AARCH64 support
// is enabled for non-mobile platforms, but on mobile platforms there
// are downstream iteration order effects that have not yet been resolved.
//
// If FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE differs across compilation
// units the program will fail to link due to a missing definition of
// folly::container::detail::F14LinkCheck<X>::check() for some X.
#if (FOLLY_SSE >= 2 || (FOLLY_NEON && FOLLY_AARCH64)) && !FOLLY_MOBILE
#define FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE 1 #define FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE 1
#else #else
#define FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE 0 #define FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE 0
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <boost/interprocess/allocators/adaptive_pool.hpp> #include <boost/interprocess/allocators/adaptive_pool.hpp>
#include <boost/interprocess/managed_shared_memory.hpp> #include <boost/interprocess/managed_shared_memory.hpp>
#include <folly/Format.h> #include <folly/Format.h>
#include <folly/Random.h> #include <folly/Random.h>
#include <folly/Traits.h> #include <folly/Traits.h>
...@@ -174,29 +175,34 @@ TEST(ShmF14VectorSet, simple) { ...@@ -174,29 +175,34 @@ TEST(ShmF14VectorSet, simple) {
template <typename M> template <typename M>
void runSimultaneousAccessMapTest() { void runSimultaneousAccessMapTest() {
auto name = makeRandomName(); using namespace folly::f14::detail;
auto segment1 = makeShmSegment(8192, name);
auto segment2 = // fallback std::unordered_map on libstdc++ doesn't pass this test
std::make_shared<managed_shared_memory>(open_only, name.c_str()); if (getF14IntrinsicsMode() != F14IntrinsicsMode::None) {
auto name = makeRandomName();
auto m1 = segment1->construct<M>("m")( auto segment1 = makeShmSegment(8192, name);
typename M::allocator_type{segment1->get_segment_manager()}); auto segment2 =
auto m2 = segment2->find<M>("m").first; std::make_shared<managed_shared_memory>(open_only, name.c_str());
std::cout << "m in segment1 @ " << (uintptr_t)m1 << "\n"; auto m1 = segment1->construct<M>("m")(
std::cout << "m in segment2 @ " << (uintptr_t)m2 << "\n"; typename M::allocator_type{segment1->get_segment_manager()});
auto m2 = segment2->find<M>("m").first;
EXPECT_NE(&*m1, &*m2);
std::cout << "m in segment1 @ " << (uintptr_t)m1 << "\n";
(*m1)[1] = 10; std::cout << "m in segment2 @ " << (uintptr_t)m2 << "\n";
EXPECT_EQ(m2->count(0), 0);
EXPECT_EQ((*m2)[1], 10); EXPECT_NE(&*m1, &*m2);
(*m2)[2] = 20;
EXPECT_EQ(m1->size(), 2); (*m1)[1] = 10;
EXPECT_EQ(m1->find(2)->second, 20); EXPECT_EQ(m2->count(0), 0);
(*m1)[3] = 30; EXPECT_EQ((*m2)[1], 10);
EXPECT_EQ(m2->size(), 3); (*m2)[2] = 20;
EXPECT_FALSE(m2->try_emplace(3, 33).second); EXPECT_EQ(m1->size(), 2);
EXPECT_EQ(m1->find(2)->second, 20);
(*m1)[3] = 30;
EXPECT_EQ(m2->size(), 3);
EXPECT_FALSE(m2->emplace(std::make_pair(3, 33)).second);
}
} }
TEST(ShmF14ValueMap, simultaneous) { TEST(ShmF14ValueMap, simultaneous) {
......
...@@ -57,19 +57,32 @@ template < ...@@ -57,19 +57,32 @@ template <
typename V> typename V>
void runAllocatedMemorySizeTest() { void runAllocatedMemorySizeTest() {
using namespace folly::f14; using namespace folly::f14;
using namespace folly::f14::detail;
using A = SwapTrackingAlloc<std::pair<const K, V>>; using A = SwapTrackingAlloc<std::pair<const K, V>>;
resetTracking(); resetTracking();
{ {
TMap<K, V, DefaultHasher<K>, DefaultKeyEqual<K>, A> m; TMap<K, V, DefaultHasher<K>, DefaultKeyEqual<K>, A> m;
EXPECT_EQ(testAllocatedMemorySize, m.getAllocatedMemorySize());
// 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) {
EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(m.getAllocatedMemorySize(), 0);
}
auto emptyMapAllocatedMemorySize = testAllocatedMemorySize; auto emptyMapAllocatedMemorySize = testAllocatedMemorySize;
auto emptyMapAllocatedBlockCount = testAllocatedBlockCount; auto emptyMapAllocatedBlockCount = testAllocatedBlockCount;
for (size_t i = 0; i < 1000; ++i) { for (size_t i = 0; i < 1000; ++i) {
m.insert(std::make_pair(folly::to<K>(i), V{})); m.insert(std::make_pair(folly::to<K>(i), V{}));
m.erase(folly::to<K>(i / 10 + 2)); m.erase(folly::to<K>(i / 10 + 2));
EXPECT_EQ(testAllocatedMemorySize, m.getAllocatedMemorySize()); if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, m.getAllocatedMemorySize());
}
EXPECT_GE(m.getAllocatedMemorySize(), sizeof(std::pair<K, V>) * m.size());
std::size_t size = 0; std::size_t size = 0;
std::size_t count = 0; std::size_t count = 0;
m.visitAllocationClasses([&](std::size_t, std::size_t) mutable {}); m.visitAllocationClasses([&](std::size_t, std::size_t) mutable {});
...@@ -77,13 +90,20 @@ void runAllocatedMemorySizeTest() { ...@@ -77,13 +90,20 @@ void runAllocatedMemorySizeTest() {
size += bytes * n; size += bytes * n;
count += n; count += n;
}); });
EXPECT_EQ(testAllocatedMemorySize, size); if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedBlockCount, count); EXPECT_EQ(testAllocatedMemorySize, size);
EXPECT_EQ(testAllocatedBlockCount, count);
}
} }
m = decltype(m){}; m = decltype(m){};
EXPECT_EQ(testAllocatedMemorySize, emptyMapAllocatedMemorySize); EXPECT_EQ(testAllocatedMemorySize, emptyMapAllocatedMemorySize);
EXPECT_EQ(testAllocatedBlockCount, emptyMapAllocatedBlockCount); EXPECT_EQ(testAllocatedBlockCount, emptyMapAllocatedBlockCount);
m.reserve(5);
EXPECT_GT(testAllocatedMemorySize, 0);
m = {};
EXPECT_GT(testAllocatedMemorySize, 0);
} }
EXPECT_EQ(testAllocatedMemorySize, 0); EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(testAllocatedBlockCount, 0); EXPECT_EQ(testAllocatedBlockCount, 0);
......
...@@ -53,37 +53,53 @@ template < ...@@ -53,37 +53,53 @@ template <
typename K> typename K>
void runAllocatedMemorySizeTest() { void runAllocatedMemorySizeTest() {
using namespace folly::f14; using namespace folly::f14;
using namespace folly::f14::detail;
using A = SwapTrackingAlloc<K>; using A = SwapTrackingAlloc<K>;
resetTracking(); resetTracking();
{ {
TSet<K, DefaultHasher<K>, DefaultKeyEqual<K>, A> m; TSet<K, DefaultHasher<K>, DefaultKeyEqual<K>, A> s;
EXPECT_EQ(testAllocatedMemorySize, m.getAllocatedMemorySize());
// 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) {
EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(s.getAllocatedMemorySize(), 0);
}
auto emptySetAllocatedMemorySize = testAllocatedMemorySize; auto emptySetAllocatedMemorySize = testAllocatedMemorySize;
auto emptySetAllocatedBlockCount = testAllocatedBlockCount; auto emptySetAllocatedBlockCount = testAllocatedBlockCount;
// TODO(T33426422): check unordered_set impl in Android NDK for allocation
// behaviors
#if (!FOLLY_MOBILE || defined(__APPLE__))
for (size_t i = 0; i < 1000; ++i) { for (size_t i = 0; i < 1000; ++i) {
m.insert(folly::to<K>(i)); s.insert(folly::to<K>(i));
m.erase(folly::to<K>(i / 10 + 2)); s.erase(folly::to<K>(i / 10 + 2));
EXPECT_EQ(testAllocatedMemorySize, m.getAllocatedMemorySize()); if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, s.getAllocatedMemorySize());
}
EXPECT_GE(s.getAllocatedMemorySize(), sizeof(K) * s.size());
std::size_t size = 0; std::size_t size = 0;
std::size_t count = 0; std::size_t count = 0;
m.visitAllocationClasses([&](std::size_t, std::size_t) mutable {}); s.visitAllocationClasses([&](std::size_t, std::size_t) mutable {});
m.visitAllocationClasses([&](std::size_t bytes, std::size_t n) { s.visitAllocationClasses([&](std::size_t bytes, std::size_t n) {
size += bytes * n; size += bytes * n;
count += n; count += n;
}); });
EXPECT_EQ(testAllocatedMemorySize, size); if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedBlockCount, count); EXPECT_EQ(testAllocatedMemorySize, size);
EXPECT_EQ(testAllocatedBlockCount, count);
}
} }
#endif
m = decltype(m){}; s = decltype(s){};
EXPECT_EQ(testAllocatedMemorySize, emptySetAllocatedMemorySize); EXPECT_EQ(testAllocatedMemorySize, emptySetAllocatedMemorySize);
EXPECT_EQ(testAllocatedBlockCount, emptySetAllocatedBlockCount); EXPECT_EQ(testAllocatedBlockCount, emptySetAllocatedBlockCount);
s.reserve(5);
EXPECT_GT(testAllocatedMemorySize, emptySetAllocatedMemorySize);
s = {};
EXPECT_GT(testAllocatedMemorySize, emptySetAllocatedMemorySize);
} }
EXPECT_EQ(testAllocatedMemorySize, 0); EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(testAllocatedBlockCount, 0); EXPECT_EQ(testAllocatedBlockCount, 0);
......
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