Commit 3411b8c6 authored by Nathan Bronson's avatar Nathan Bronson Committed by Facebook Github Bot

fix empty baseclass optimization for F14 on libc++

Summary:
F14 previously had a space optimization that relied on the fact
that tuple<A,B,C> was std::is_empty if each of A, B, and C was empty
(and non-final), but this is not the case on libc++.  This diff removes
the use of std::tuple and manually packages the hash table functors to
enable the Empty Baseclass Optimization.

Reviewed By: yfeldblum

Differential Revision: D9507243

fbshipit-source-id: 3ee8aa2aa444d91de56210662c86e555406d8eca
parent 1aac76b0
......@@ -197,7 +197,8 @@ class F14BasicMap {
allocator_type const& alloc)
: F14BasicMap(initialCapacity, hash, key_equal{}, alloc) {}
explicit F14BasicMap(allocator_type const& alloc) : F14BasicMap(0, alloc) {}
explicit F14BasicMap(allocator_type const& alloc)
: F14BasicMap(0, hasher{}, key_equal{}, alloc) {}
template <typename InputIt>
F14BasicMap(
......
......@@ -191,7 +191,8 @@ class F14BasicSet {
allocator_type const& alloc)
: F14BasicSet(initialCapacity, hash, key_equal{}, alloc) {}
explicit F14BasicSet(allocator_type const& alloc) : F14BasicSet(0, alloc) {}
explicit F14BasicSet(allocator_type const& alloc)
: F14BasicSet(0, hasher{}, key_equal{}, alloc) {}
template <typename InputIt>
F14BasicSet(
......
......@@ -17,7 +17,6 @@
#pragma once
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
......@@ -48,6 +47,39 @@ using SetOrMapValueType = std::conditional_t<
KeyType,
MapValueType<KeyType, MappedTypeOrVoid>>;
// Used to enable EBO for Hasher, KeyEqual, and Alloc. std::tuple of
// all empty objects is empty in libstdc++ but not libc++.
template <
char Tag,
typename T,
bool Inherit = std::is_empty<T>::value && !std::is_final<T>::value>
struct ObjectHolder {
T value_;
template <typename... Args>
ObjectHolder(Args&&... args) : value_{std::forward<Args>(args)...} {}
T& operator*() {
return value_;
}
T const& operator*() const {
return value_;
}
};
template <char Tag, typename T>
struct ObjectHolder<Tag, T, true> : private T {
template <typename... Args>
ObjectHolder(Args&&... args) : T{std::forward<Args>(args)...} {}
T& operator*() {
return *this;
}
T const& operator*() const {
return *this;
}
};
// Policy provides the functionality of hasher, key_equal, and
// allocator_type. In addition, it can add indirection to the values
// contained in the base table by defining a non-trivial value() method.
......@@ -76,9 +108,14 @@ template <
typename AllocOrVoid,
typename ItemType>
struct BasePolicy
: std::tuple<
Defaulted<HasherOrVoid, DefaultHasher<KeyType>>,
Defaulted<KeyEqualOrVoid, DefaultKeyEqual<KeyType>>,
: private ObjectHolder<
'H',
Defaulted<HasherOrVoid, DefaultHasher<KeyType>>>,
private ObjectHolder<
'E',
Defaulted<KeyEqualOrVoid, DefaultKeyEqual<KeyType>>>,
private ObjectHolder<
'A',
Defaulted<
AllocOrVoid,
DefaultAlloc<SetOrMapValueType<KeyType, MappedTypeOrVoid>>>> {
......@@ -104,6 +141,10 @@ struct BasePolicy
"wrong allocator value_type");
private:
using HasherHolder = ObjectHolder<'H', Hasher>;
using KeyEqualHolder = ObjectHolder<'E', KeyEqual>;
using AllocHolder = ObjectHolder<'A', Alloc>;
// emulate c++17's std::allocator_traits<A>::is_always_equal
template <typename A, typename = void>
......@@ -139,8 +180,6 @@ struct BasePolicy
using InternalSizeType = std::size_t;
using Super = std::tuple<Hasher, KeyEqual, Alloc>;
// if false, F14Table will be smaller but F14Table::begin() won't work
static constexpr bool kEnableItemIteration = true;
......@@ -159,24 +198,28 @@ struct BasePolicy
//////// methods
BasePolicy(Hasher const& hasher, KeyEqual const& keyEqual, Alloc const& alloc)
: Super{hasher, keyEqual, alloc} {}
: HasherHolder{hasher}, KeyEqualHolder{keyEqual}, AllocHolder{alloc} {}
BasePolicy(BasePolicy const& rhs)
: Super{rhs.hasher(),
rhs.keyEqual(),
AllocTraits::select_on_container_copy_construction(rhs.alloc())} {
}
: HasherHolder{rhs.hasher()},
KeyEqualHolder{rhs.keyEqual()},
AllocHolder{
AllocTraits::select_on_container_copy_construction(rhs.alloc())} {}
BasePolicy(BasePolicy const& rhs, Alloc const& alloc)
: Super{rhs.hasher(), rhs.keyEqual(), alloc} {}
: HasherHolder{rhs.hasher()},
KeyEqualHolder{rhs.keyEqual()},
AllocHolder{alloc} {}
BasePolicy(BasePolicy&& rhs) noexcept
: Super{std::move(rhs.hasher()),
std::move(rhs.keyEqual()),
std::move(rhs.alloc())} {}
: HasherHolder{std::move(rhs.hasher())},
KeyEqualHolder{std::move(rhs.keyEqual())},
AllocHolder{std::move(rhs.alloc())} {}
BasePolicy(BasePolicy&& rhs, Alloc const& alloc) noexcept
: Super{std::move(rhs.hasher()), std::move(rhs.keyEqual()), alloc} {}
: HasherHolder{std::move(rhs.hasher())},
KeyEqualHolder{std::move(rhs.keyEqual())},
AllocHolder{alloc} {}
BasePolicy& operator=(BasePolicy const& rhs) {
hasher() = rhs.hasher();
......@@ -206,22 +249,22 @@ struct BasePolicy
}
Hasher& hasher() {
return std::get<0>(*this);
return *static_cast<HasherHolder&>(*this);
}
Hasher const& hasher() const {
return std::get<0>(*this);
return *static_cast<HasherHolder const&>(*this);
}
KeyEqual& keyEqual() {
return std::get<1>(*this);
return *static_cast<KeyEqualHolder&>(*this);
}
KeyEqual const& keyEqual() const {
return std::get<1>(*this);
return *static_cast<KeyEqualHolder const&>(*this);
}
Alloc& alloc() {
return std::get<2>(*this);
return *static_cast<AllocHolder&>(*this);
}
Alloc const& alloc() const {
return std::get<2>(*this);
return *static_cast<AllocHolder const&>(*this);
}
template <typename K>
......
......@@ -483,8 +483,6 @@ TEST(F14FastSet, simple) {
}
TEST(F14Set, ContainerSize) {
// TODO(T33121191): pending investigation on EBO applicability on ios platforms.
#if !FOLLY_MOBILE || !__APPLE__
{
folly::F14ValueSet<int> set;
set.insert(10);
......@@ -515,7 +513,6 @@ TEST(F14Set, ContainerSize) {
EXPECT_EQ(sizeof(set), 8 + 2 * sizeof(void*));
EXPECT_EQ(set.getAllocatedMemorySize(), 32);
}
#endif
}
TEST(F14VectorMap, reverse_iterator) {
......
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