Commit a32a7701 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

specialize Function internals over trivially-copyable callables

Summary:
`Function` requires an instantiation of a control function for every unique callable type. There are two control functions, named `execSmall` and `execBig`.

But instantiation is expensive for build speed and build size - especially instantiation per unique callable type.

An hypothesis is that it is common for callable types to be trivially-copyable. In particular, that it is common to have lambdas with implicit by-reference captures and no other captures, as `[&] { /* ... */ }`.

So we introduce a new dispatch class, `DispatchSmallTrivial`, with a member function `exec` which is instantiated as few times as possible and which is used for all trivially-copyable callable types.

Reviewed By: ot, luciang

Differential Revision: D32932586

fbshipit-source-id: 34ecad6b6bcbbc3ed379e3d32babf04c91d76557
parent 6c475cc6
......@@ -218,6 +218,7 @@
#pragma once
#include <cstring>
#include <functional>
#include <memory>
#include <new>
......@@ -580,6 +581,36 @@ using Exec = decltype(&exec_);
static_assert(noexcept(Exec(nullptr)(Op{}, nullptr, nullptr)), "");
#endif
// This is intentionally instantiated per size rather than per function in order
// to minimize the number of instantiations. It would be safe to minimize
// instantiations even more by simply having a single non-template function that
// copies sizeof(Data) bytes rather than only copying sizeof(Fun) bytes, but
// then for small function types it would be likely to cross cache lines without
// need. But it is only necessary to handle those sizes which are multiples of
// the alignof(Data), and to round up other sizes.
struct DispatchSmallTrivial {
template <typename Fun, typename Base>
static constexpr auto call = Base::template callSmall<Fun>;
template <std::size_t Size>
static std::size_t exec_(Op o, Data* src, Data* dst) noexcept {
switch (o) {
case Op::MOVE:
std::memcpy(static_cast<void*>(dst), static_cast<void*>(src), Size);
break;
case Op::NUKE:
break;
case Op::HEAP:
break;
}
return 0U;
}
template <std::size_t size, std::size_t adjust = alignof(Data) - 1>
static constexpr std::size_t size_ = (size + adjust) & ~adjust;
template <typename Fun>
static constexpr auto exec = exec_<size_<sizeof(Fun)>>;
};
struct DispatchSmall {
template <typename Fun, typename Base>
static constexpr auto call = Base::template callSmall<Fun>;
......@@ -667,7 +698,7 @@ class Function final : private detail::function::FunctionTraits<FunctionType> {
if (fun) {
data_.big = new Fun(static_cast<Fun&&>(fun));
call_ = Traits::template callBig<Fun>;
exec_ = detail::function::DispatchBig::exec<Fun>;
exec_ = Exec(detail::function::DispatchBig::exec<Fun>);
}
}
......@@ -738,9 +769,12 @@ class Function final : private detail::function::FunctionTraits<FunctionType> {
/* implicit */ Function(Fun fun) noexcept(
IsSmall<Fun>::value&& noexcept(Fun(std::declval<Fun>()))) {
using Dispatch = conditional_t<
IsSmall<Fun>::value,
detail::function::DispatchSmall,
detail::function::DispatchBig>;
IsSmall<Fun>::value && is_trivially_copyable_v<Fun>,
detail::function::DispatchSmallTrivial,
conditional_t<
IsSmall<Fun>::value,
detail::function::DispatchSmall,
detail::function::DispatchBig>>;
if (detail::function::isEmptyFunction(fun)) {
return;
}
......@@ -750,7 +784,7 @@ class Function final : private detail::function::FunctionTraits<FunctionType> {
data_.big = new Fun(static_cast<Fun&&>(fun));
}
call_ = Dispatch::template call<Fun, Traits>;
exec_ = Dispatch::template exec<Fun>;
exec_ = Exec(Dispatch::template exec<Fun>);
}
/**
......
......@@ -1227,3 +1227,44 @@ TEST(Function, AllocatedSize) {
<< "Lambda-derived Function's allocated size is smaller than the "
"lambda's capture size";
}
TEST(Function, TrivialSmallBig) {
auto tl = [] { return 7; };
static_assert(std::is_trivially_copyable_v<decltype(tl)>);
struct move_nx {
move_nx() {}
~move_nx() {}
move_nx(move_nx&&) noexcept {}
void operator=(move_nx&&) = delete;
};
auto sl = [o = move_nx{}] { return 7; };
static_assert(!std::is_trivially_copyable_v<decltype(sl)>);
static_assert(std::is_nothrow_move_constructible_v<decltype(sl)>);
struct move_x {
move_x() {}
~move_x() {}
move_x(move_x&&) noexcept(false) {}
void operator=(move_x&&) = delete;
};
auto hl = [o = move_x{}] { return 7; };
static_assert(!std::is_trivially_copyable_v<decltype(hl)>);
static_assert(!std::is_nothrow_move_constructible_v<decltype(hl)>);
Function<int()> t{std::move(tl)};
Function<int()> s{std::move(sl)};
Function<int()> h{std::move(hl)};
EXPECT_EQ(7, t());
EXPECT_EQ(7, s());
EXPECT_EQ(7, h());
auto t2 = std::move(t);
auto s2 = std::move(s);
auto h2 = std::move(h);
EXPECT_EQ(7, t2());
EXPECT_EQ(7, s2());
EXPECT_EQ(7, h2());
}
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