Commit 8dfae42b authored by Nathan Bronson's avatar Nathan Bronson Committed by Facebook Github Bot

F14 enable trivial object optimizations for custom allocators

Summary:
F14 can apply some additional optimizations for trivially
copyable and trivially destructible values in the case that the allocator
doesn't do anything special in its construct and destroy methods.
Previously this was gated based on whether the allocator was exactly
std::allocator, but that causes this optimization to be missed in some
important cases.  This diff extends the checking machinery to test if
there is actually a default construct or destroy method in the allocator
(they are optional) and to test if there is a manual override of the
conservative default check.

Reviewed By: yfeldblum

Differential Revision: D8191437

fbshipit-source-id: c55b8f32427c3f0eff5c8ef42d7b51a57b0cbd1f
parent d9df4bf9
......@@ -488,6 +488,81 @@ template <typename T, class Alloc>
struct AllocatorHasTrivialDeallocate<CxxAllocatorAdaptor<T, Alloc>>
: AllocatorHasTrivialDeallocate<Alloc> {};
namespace detail {
// note that construct and destroy here are methods, not short names for
// the constructor and destructor
FOLLY_CREATE_MEMBER_INVOKE_TRAITS(AllocatorConstruct_, construct);
FOLLY_CREATE_MEMBER_INVOKE_TRAITS(AllocatorDestroy_, destroy);
template <typename Void, typename Alloc, typename... Args>
struct AllocatorCustomizesConstruct_
: AllocatorConstruct_::template is_invocable<Alloc, Args...> {};
template <typename Alloc, typename... Args>
struct AllocatorCustomizesConstruct_<
void_t<typename Alloc::folly_has_default_object_construct>,
Alloc,
Args...> : Negation<typename Alloc::folly_has_default_object_construct> {};
template <typename Void, typename Alloc, typename... Args>
struct AllocatorCustomizesDestroy_
: AllocatorDestroy_::template is_invocable<Alloc, Args...> {};
template <typename Alloc, typename... Args>
struct AllocatorCustomizesDestroy_<
void_t<typename Alloc::folly_has_default_object_destroy>,
Alloc,
Args...> : Negation<typename Alloc::folly_has_default_object_destroy> {};
} // namespace detail
/**
* AllocatorHasDefaultObjectConstruct
*
* AllocatorHasDefaultObjectConstruct<A, T, Args...> unambiguously
* inherits std::integral_constant<bool, V>, where V will be true iff
* the effect of std::allocator_traits<A>::construct(a, p, args...) is
* the same as new (static_cast<void*>(p)) T(args...). If true then
* any optimizations applicable to object construction (relying on
* std::is_trivially_copyable<T>, for example) can be applied to objects
* in an allocator-aware container using an allocation of type A.
*
* Allocator types can override V by declaring a type alias for
* folly_has_default_object_construct. It is helpful to do this if you
* define a custom allocator type that defines a construct method, but
* that method doesn't do anything except call placement new.
*/
template <typename Alloc, typename T, typename... Args>
struct AllocatorHasDefaultObjectConstruct
: Negation<
detail::AllocatorCustomizesConstruct_<void, Alloc, T*, Args...>> {};
template <typename Value, typename T, typename... Args>
struct AllocatorHasDefaultObjectConstruct<std::allocator<Value>, T, Args...>
: std::true_type {};
/**
* AllocatorHasDefaultObjectDestroy
*
* AllocatorHasDefaultObjectDestroy<A, T> unambiguously inherits
* std::integral_constant<bool, V>, where V will be true iff the effect
* of std::allocator_traits<A>::destroy(a, p) is the same as p->~T().
* If true then optimizations applicable to object destruction (relying
* on std::is_trivially_destructible<T>, for example) can be applied to
* objects in an allocator-aware container using an allocator of type A.
*
* Allocator types can override V by declaring a type alias for
* folly_has_default_object_destroy. It is helpful to do this if you
* define a custom allocator type that defines a destroy method, but that
* method doesn't do anything except call the object's destructor.
*/
template <typename Alloc, typename T>
struct AllocatorHasDefaultObjectDestroy
: Negation<detail::AllocatorCustomizesDestroy_<void, Alloc, T*>> {};
template <typename Value, typename T>
struct AllocatorHasDefaultObjectDestroy<std::allocator<Value>, T>
: std::true_type {};
/*
* folly::enable_shared_from_this
*
......
......@@ -21,6 +21,7 @@
#include <type_traits>
#include <utility>
#include <folly/Memory.h>
#include <folly/Unit.h>
#include <folly/container/detail/F14Table.h>
#include <folly/hash/Hash.h>
......@@ -442,7 +443,7 @@ class ValueContainerPolicy : public BasePolicy<
static constexpr bool destroyItemOnClear() {
return !std::is_trivially_destructible<Item>::value ||
!std::is_same<Alloc, std::allocator<Value>>::value;
!folly::AllocatorHasDefaultObjectDestroy<Alloc, Item>::value;
}
// inherit constructors
......@@ -915,6 +916,15 @@ class VectorContainerPolicy : public BasePolicy<
return false;
}
private:
static constexpr bool valueIsTriviallyCopyable() {
return folly::AllocatorHasDefaultObjectConstruct<Alloc, Value, Value>::
value &&
folly::AllocatorHasDefaultObjectDestroy<Alloc, Value>::value &&
FOLLY_IS_TRIVIALLY_COPYABLE(Value);
}
public:
VectorContainerPolicy(
Hasher const& hasher,
KeyEqual const& keyEqual,
......@@ -1058,8 +1068,7 @@ class VectorContainerPolicy : public BasePolicy<
complainUnlessNothrowMove<Key>();
complainUnlessNothrowMove<lift_unit_t<MappedTypeOrVoid>>();
if (std::is_same<Alloc, std::allocator<Value>>::value &&
FOLLY_IS_TRIVIALLY_COPYABLE(Value)) {
if (valueIsTriviallyCopyable()) {
std::memcpy(dst, src, n * sizeof(Value));
} else {
for (std::size_t i = 0; i < n; ++i, ++src, ++dst) {
......@@ -1083,8 +1092,7 @@ class VectorContainerPolicy : public BasePolicy<
auto src = std::addressof(rhs.values_[0]);
Value* dst = std::addressof(values_[0]);
if (std::is_same<Alloc, std::allocator<Value>>::value &&
FOLLY_IS_TRIVIALLY_COPYABLE(Value)) {
if (valueIsTriviallyCopyable()) {
std::memcpy(dst, src, size * sizeof(Value));
} else {
for (std::size_t i = 0; i < size; ++i, ++src, ++dst) {
......
......@@ -165,6 +165,106 @@ TEST(allocate_sys_buffer, compiles) {
// Freed at the end of the scope.
}
namespace {
template <typename T>
struct TestAlloc1 : SysAllocator<T> {
template <typename U, typename... Args>
void construct(U* p, Args&&... args) {
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
}
};
template <typename T>
struct TestAlloc2 : TestAlloc1<T> {
template <typename U>
void destroy(U* p) {
p->~U();
}
};
template <typename T>
struct TestAlloc3 : TestAlloc2<T> {
using folly_has_default_object_construct = std::true_type;
};
template <typename T>
struct TestAlloc4 : TestAlloc3<T> {
using folly_has_default_object_destroy = std::true_type;
};
template <typename T>
struct TestAlloc5 : SysAllocator<T> {
using folly_has_default_object_construct = std::true_type;
using folly_has_default_object_destroy = std::false_type;
};
} // namespace
TEST(AllocatorObjectLifecycleTraits, compiles) {
using A = std::allocator<int>;
using S = std::string;
static_assert(
folly::AllocatorHasDefaultObjectConstruct<A, int, int>::value, "");
static_assert(folly::AllocatorHasDefaultObjectConstruct<A, S, S>::value, "");
static_assert(folly::AllocatorHasDefaultObjectDestroy<A, int>::value, "");
static_assert(folly::AllocatorHasDefaultObjectDestroy<A, S>::value, "");
static_assert(
folly::AllocatorHasDefaultObjectConstruct<
folly::AlignedSysAllocator<int>,
int,
int>::value,
"");
static_assert(
folly::AllocatorHasDefaultObjectConstruct<
folly::AlignedSysAllocator<int>,
S,
S>::value,
"");
static_assert(
folly::AllocatorHasDefaultObjectDestroy<
folly::AlignedSysAllocator<int>,
int>::value,
"");
static_assert(
folly::AllocatorHasDefaultObjectDestroy<
folly::AlignedSysAllocator<int>,
S>::value,
"");
static_assert(
!folly::AllocatorHasDefaultObjectConstruct<TestAlloc1<S>, S, S>::value,
"");
static_assert(
folly::AllocatorHasDefaultObjectDestroy<TestAlloc1<S>, S>::value, "");
static_assert(
!folly::AllocatorHasDefaultObjectConstruct<TestAlloc2<S>, S, S>::value,
"");
static_assert(
!folly::AllocatorHasDefaultObjectDestroy<TestAlloc2<S>, S>::value, "");
static_assert(
folly::AllocatorHasDefaultObjectConstruct<TestAlloc3<S>, S, S>::value,
"");
static_assert(
!folly::AllocatorHasDefaultObjectDestroy<TestAlloc3<S>, S>::value, "");
static_assert(
folly::AllocatorHasDefaultObjectConstruct<TestAlloc4<S>, S, S>::value,
"");
static_assert(
folly::AllocatorHasDefaultObjectDestroy<TestAlloc4<S>, S>::value, "");
static_assert(
folly::AllocatorHasDefaultObjectConstruct<TestAlloc5<S>, S, S>::value,
"");
static_assert(
!folly::AllocatorHasDefaultObjectDestroy<TestAlloc5<S>, S>::value, "");
}
template <typename C>
static void test_enable_shared_from_this(std::shared_ptr<C> sp) {
ASSERT_EQ(1l, sp.use_count());
......
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