Skip to content

Commit

Permalink
Speed up reflection and row operations (#23)
Browse files Browse the repository at this point in the history
Building reflection for types that have a lot of field tags is currently
pretty slow. After this patch it will still be slow, but a lot of
unnecessary work has been removed. In particular, `best::row::apply` is
now implemented directly as a primitive in `best::row_internal::impl`,
which means each call does not slam `best::row::get` N times.

Profiling suggests a lot of time is wasted instantiating copies of
`best::tlist`; that may be a worthwhile target for optimizing down the
line (either by making instantiation cheaper or reducing the number of
instantiations; it looks like it's currently linear-ish but there are
almost certainly avoidable quadratic instantiations lurking).

Additionally, this change fixes bugs in `best::vec`, `best::overflow`,
and `best::atoi` that this optimization refactor uncovered, and
introduces `best::iter::take()`, which I needed for debugging one of the
bugs. It also adds the `best::abridged` concept for detecting whether a
type is a `best::abridged<T>`.
  • Loading branch information
mcy authored Jul 11, 2024
1 parent a3dcfe7 commit c0e02c1
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 229 deletions.
136 changes: 48 additions & 88 deletions best/container/internal/row.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,94 +27,28 @@
#include "best/meta/tlist.h"

namespace best::row_internal {
// This builds a set of lookup indices that find the first index that matches
// lookup up a column type or column name.
template <size_t n, typename T>
struct entry {
size_t table[n];
size_t total = 0;

constexpr void append(size_t m) { table[total++] = m; }
};

template <typename T>
concept has_col_name = requires {
template <typename K, typename T>
concept has_key = std::is_same_v<K, T> || requires {
typename T::BestRowKey;
requires(!best::same<T, typename T::BestRowKey>);
};

template <size_t n, typename... Ts>
struct lookup_table final : entry<n, Ts>... {
size_t next_idx = 0;

template <typename T>
constexpr auto operator+(entry<n, T>*)
requires(!has_col_name<T>)
{
if constexpr (std::is_base_of_v<entry<n, T>, lookup_table>) {
static_cast<entry<n, T>&>(*this).append(next_idx++);
return *this;
} else {
return lookup_table<n, Ts..., T>{
entry<n, Ts>(*this)...,
entry<n, T>{{next_idx}, 1},
next_idx + 1,
};
}
}

template <typename T>
constexpr auto operator+(entry<n, T>*)
requires has_col_name<T>
{
using K = T::BestRowKey;
if constexpr (std::is_base_of_v<entry<n, T>, lookup_table>) {
static_cast<entry<n, T>&>(*this).append(next_idx);
static_cast<entry<n, K>&>(*this).append(next_idx++);
return *this;
} else if constexpr (std::is_base_of_v<entry<n, K>, lookup_table>) {
static_cast<entry<n, K>&>(*this).append(next_idx++);
return lookup_table<n, Ts..., T>{
entry<n, Ts>(*this)...,
entry<n, T>{{next_idx}, 1},
next_idx + 1,
};
} else {
return lookup_table<n, Ts..., T, K>{
entry<n, Ts>(*this)...,
entry<n, T>{{next_idx}, 1},
entry<n, K>{{next_idx}, 1},
next_idx + 1,
};
}
}

template <typename T>
constexpr size_t count() const {
if constexpr (std::is_base_of_v<entry<n, T>, lookup_table>) {
return static_cast<const entry<n, T>&>(*this).total;
}
return {};
}
requires std::is_same_v<K, typename T::BestRowKey>;
};

template <typename... Ts>
inline constexpr auto lookup =
(lookup_table<sizeof...(Ts)>{} + ... + (entry<sizeof...(Ts), Ts>*){});

template <typename K, typename... Ts, const auto& lut = lookup<Ts...>,
size_t count = lut.template count<K>()>
/// Prepares a lookup table for finding `K` among `Ts`. This returns an array
/// whose first element is the number of matching elements `n`, and whose next
/// `n` elements are their indices among the `Ts`.
template <typename K, typename... Ts>
inline constexpr auto lookup = [] {
size_t next = 0;
std::array<size_t, sizeof...(Ts) + 1> table = {};
((has_key<K, Ts> ? table[1 + table[0]++] = next++ : next++), ...);
return table;
}();

template <typename K, typename... Ts, const auto& lut = lookup<K, Ts...>,
size_t count = lut[0]>
inline constexpr auto do_lookup() {
if constexpr (count == 0) {
return best::vals<>;
} else {
const size_t(&table)[sizeof...(Ts)] =
static_cast<const entry<sizeof...(Ts), K>&>(lut).table;

return [&]<size_t... i>(std::index_sequence<i...>) {
return best::vals<table[i]...>;
}(std::make_index_sequence<count>{});
}
return best::indices<lut[0]>.apply(
[]<size_t... i> { return best::vals<lut[i + 1]...>; });
}

template <size_t i, typename T>
Expand All @@ -126,13 +60,19 @@ template <typename, typename...>
struct impl;

template <typename I>
struct impl<I> {};
struct impl<I> {
constexpr decltype(auto) apply_impl(auto&& cb) const { return cb(); }
};

template <typename I, typename A>
struct impl<I, A> {
constexpr const auto& get_impl(best::index_t<0>) const { return x0; }
constexpr auto& get_impl(best::index_t<0>) { return x0; }
best::object<A> x0;

constexpr decltype(auto) apply_impl(auto&& cb) const { return cb(x0); }
constexpr decltype(auto) apply_impl(auto&& cb) { return cb(x0); }

[[no_unique_address]] best::object<A> x0;
};

template <typename I, typename A, typename B>
Expand All @@ -141,8 +81,12 @@ struct impl<I, A, B> {
constexpr auto& get_impl(best::index_t<0>) { return x0; }
constexpr const auto& get_impl(best::index_t<1>) const { return x1; }
constexpr auto& get_impl(best::index_t<1>) { return x1; }
best::object<A> x0;
best::object<B> x1;

constexpr decltype(auto) apply_impl(auto&& cb) const { return cb(x0, x1); }
constexpr decltype(auto) apply_impl(auto&& cb) { return cb(x0, x1); }

[[no_unique_address]] best::object<A> x0;
[[no_unique_address]] best::object<B> x1;
};

template <size_t... i, typename... Elems>
Expand All @@ -158,8 +102,24 @@ struct impl<const best::vlist<i...>, Elems...> : elem<i, Elems>... {
using T = best::tlist<Elems...>::template type<j>;
return static_cast<elem<j, T>&>(*this).value;
}

constexpr decltype(auto) apply_impl(auto cb) const {
return cb(static_cast<const elem<i, Elems>&>(*this).value...);
}
constexpr decltype(auto) apply_impl(auto&& cb) {
return cb(static_cast<elem<i, Elems>&>(*this).value...);
}
};

constexpr decltype(auto) object_call(auto&& cb, auto&& obj) {
if constexpr (best::is_void<typename best::as_auto<decltype(obj)>::type> &&
best::callable<decltype(cb), void()>) {
best::call(static_cast<best::dependent<decltype(cb), decltype(obj)>&&>(cb));
} else {
best::call(BEST_FWD(cb), BEST_FWD(obj).or_empty());
}
}

// See tlist_internal::slice_impl().
using ::best::tlist_internal::splat;
template <typename Out, size_t... i, size_t... j, size_t... k>
Expand Down
33 changes: 33 additions & 0 deletions best/container/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,39 @@ class object final {
constexpr cptr operator->() const { return as_ptr().operator->(); }
constexpr ptr operator->() { return as_ptr().operator->(); }

/// # `object::or_empty()`
///
/// Returns the result of `operator*`, or a `best::empty&` if `T` is of void
/// type.
constexpr decltype(auto) or_empty() const& {
if constexpr (best::is_void<T>) {
return BEST_OBJECT_VALUE_;
} else {
return **this;
}
}
constexpr decltype(auto) or_empty() & {
if constexpr (best::is_void<T>) {
return BEST_OBJECT_VALUE_;
} else {
return **this;
}
}
constexpr decltype(auto) or_empty() const&& {
if constexpr (best::is_void<T>) {
return BEST_MOVE(BEST_OBJECT_VALUE_);
} else {
return *BEST_MOVE(*this);
}
}
constexpr decltype(auto) or_empty() && {
if constexpr (best::is_void<T>) {
return BEST_MOVE(BEST_OBJECT_VALUE_);
} else {
return *BEST_MOVE(*this);
}
}

// Comparisons are the obvious thing.
constexpr bool operator==(const object& that) const
requires best::equatable<T, T>
Expand Down
60 changes: 16 additions & 44 deletions best/container/row.h
Original file line number Diff line number Diff line change
Expand Up @@ -1391,75 +1391,47 @@ constexpr auto row<A...>::scatter(best::tlist<Ts...> those_types,

template <typename... A>
constexpr decltype(auto) row<A...>::apply(auto&& f) const& {
return indices.apply([&](auto... i) -> decltype(auto) {
return best::call(BEST_FWD(f), get(i)...);
});
return this->apply_impl(
[&](auto&&... p) { return best::call(BEST_FWD(f), p.or_empty()...); });
}
template <typename... A>
constexpr decltype(auto) row<A...>::apply(auto&& f) & {
return indices.apply([&](auto... i) -> decltype(auto) {
return best::call(BEST_FWD(f), get(i)...);
});
return this->apply_impl(
[&](auto&&... p) { return best::call(BEST_FWD(f), p.or_empty()...); });
}
template <typename... A>
constexpr decltype(auto) row<A...>::apply(auto&& f) const&& {
return indices.apply([&](auto... i) -> decltype(auto) {
return best::call(BEST_FWD(f), BEST_MOVE(*this).get(i)...);
return this->apply_impl([&](auto&&... p) {
return best::call(BEST_FWD(f), BEST_MOVE(p).or_empty()...);
});
}
template <typename... A>
constexpr decltype(auto) row<A...>::apply(auto&& f) && {
return indices.apply([&](auto... i) -> decltype(auto) {
return best::call(BEST_FWD(f), BEST_MOVE(*this).get(i)...);
return this->apply_impl([&](auto&&... p) {
return best::call(BEST_FWD(f), BEST_MOVE(p).or_empty()...);
});
}

template <typename... A>
constexpr void row<A...>::each(auto&& f) const& {
indices.each([&]<size_t i> {
if constexpr (best::is_void<type<i>> &&
best::callable<decltype(f), void()>) {
best::call(
static_cast<best::dependent<decltype(f), best::index_t<i>>&&>(f));
} else {
best::call(BEST_FWD(f), get(best::index<i>));
}
});
return this->apply_impl(
[&](auto&&... p) { (row_internal::object_call(BEST_FWD(f), p), ...); });
}
template <typename... A>
constexpr void row<A...>::each(auto&& f) & {
indices.each([&]<size_t i> {
if constexpr (best::is_void<type<i>> &&
best::callable<decltype(f), void()>) {
best::call(
static_cast<best::dependent<decltype(f), best::index_t<i>>&&>(f));
} else {
best::call(BEST_FWD(f), get(best::index<i>));
}
});
return this->apply_impl(
[&](auto&&... p) { (row_internal::object_call(BEST_FWD(f), p), ...); });
}
template <typename... A>
constexpr void row<A...>::each(auto&& f) const&& {
indices.each([&]<size_t i> {
if constexpr (best::is_void<type<i>> &&
best::callable<decltype(f), void()>) {
best::call(
static_cast<best::dependent<decltype(f), best::index_t<i>>&&>(f));
} else {
best::call(BEST_FWD(f), BEST_MOVE(*this).get(best::index<i>));
}
return this->apply_impl([&](auto&&... p) {
(row_internal::object_call(BEST_FWD(f), BEST_MOVE(p)), ...);
});
}
template <typename... A>
constexpr void row<A...>::each(auto&& f) && {
indices.each([&]<size_t i> {
if constexpr (best::is_void<type<i>> &&
best::callable<decltype(f), void()>) {
best::call(
static_cast<best::dependent<decltype(f), best::index_t<i>>&&>(f));
} else {
best::call(BEST_FWD(f), BEST_MOVE(*this).get(best::index<i>));
}
return this->apply_impl([&](auto&&... p) {
(row_internal::object_call(BEST_FWD(f), BEST_MOVE(p)), ...);
});
}
} // namespace best
Expand Down
4 changes: 2 additions & 2 deletions best/container/vec.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class vec final {
template <contiguous Range>
vec(alloc alloc, Range&& range)
requires best::constructible<T, best::data_type<Range>>
{
: vec(std::move(alloc)) {
assign(range);
// TODO: move optimization?
}
Expand All @@ -138,7 +138,7 @@ class vec final {
template <is_iter Iter>
vec(alloc alloc, Iter&& iter)
requires best::constructible<T, best::iter_type<Iter>>
{
: vec(std::move(alloc)) {
reserve(iter.size_hint().lower);
for (auto&& elem : iter) {
push(BEST_FWD(elem));
Expand Down
17 changes: 1 addition & 16 deletions best/func/call.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,7 @@ namespace best {
///
/// Additionally, any type parameters passed to this function will be forwarded
/// to `call`.
template <typename... Ts>
BEST_INLINE_SYNTHETIC constexpr decltype(auto) call(auto &&...args)
requires requires {
call_internal::call(call_internal::tag<Ts...>{}, BEST_FWD(args)...);
}
{
return call_internal::call(call_internal::tag<Ts...>{}, BEST_FWD(args)...);
}
template <auto... vs>
BEST_INLINE_SYNTHETIC constexpr decltype(auto) call(auto &&f, auto &&...args)
requires(sizeof...(vs) > 0) && requires {
BEST_FWD(f).template operator()<vs...>(BEST_FWD(args)...);
}
{
return BEST_FWD(f).template operator()<vs...>(BEST_FWD(args)...);
}
using ::best::call_internal::call;

/// # `best::call_devoid()`
///
Expand Down
Loading

0 comments on commit c0e02c1

Please sign in to comment.