BLI: improve support for trivial virtual arrays

This commits reduces the number of function calls through function
pointers in `blender::Any` when the stored type is trivial.

Furthermore, this implements marks some classes as trivial, which
we know are trivial but the compiler does not (the standard currently
says that any class with a virtual destructor is non-trivial). Under some
circumstances we know that final child classes are trivial though.
This allows for some optimizations.

Also see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1077r0.html.
This commit is contained in:
Jacques Lucke 2022-06-25 19:27:33 +02:00
parent 3237c6dbe8
commit 22fc0cbd69
4 changed files with 74 additions and 12 deletions

View File

@ -13,6 +13,7 @@
*/
#include <algorithm>
#include <cstring>
#include <utility>
#include "BLI_memory_utils.hh"
@ -26,6 +27,7 @@ namespace detail {
* Additional type specific #ExtraInfo can be embedded here as well.
*/
template<typename ExtraInfo> struct AnyTypeInfo {
/* The pointers are allowed to be null, which means that the implementation is trivial. */
void (*copy_construct)(void *dst, const void *src);
void (*move_construct)(void *dst, void *src);
void (*destruct)(void *src);
@ -38,10 +40,15 @@ template<typename ExtraInfo> struct AnyTypeInfo {
*/
template<typename ExtraInfo, typename T>
static constexpr AnyTypeInfo<ExtraInfo> info_for_inline = {
[](void *dst, const void *src) { new (dst) T(*(const T *)src); },
[](void *dst, void *src) { new (dst) T(std::move(*(T *)src)); },
[](void *src) { std::destroy_at(((T *)src)); },
[](const void *src) { return src; },
is_trivially_copy_constructible_extended_v<T> ?
nullptr :
+[](void *dst, const void *src) { new (dst) T(*(const T *)src); },
is_trivially_move_constructible_extended_v<T> ?
nullptr :
+[](void *dst, void *src) { new (dst) T(std::move(*(T *)src)); },
is_trivially_destructible_extended_v<T> ? nullptr :
+[](void *src) { std::destroy_at(((T *)src)); },
nullptr,
ExtraInfo::template get<T>()};
/**
@ -92,12 +99,14 @@ class Any {
using RealExtraInfo =
std::conditional_t<std::is_void_v<ExtraInfo>, detail::NoExtraInfo, ExtraInfo>;
using Info = detail::AnyTypeInfo<RealExtraInfo>;
static constexpr size_t RealInlineBufferCapacity = std::max(InlineBufferCapacity,
sizeof(std::unique_ptr<int>));
/**
* Inline buffer that either contains nothing, the stored value directly, or a #std::unique_ptr
* to the value.
*/
AlignedBuffer<std::max(InlineBufferCapacity, sizeof(std::unique_ptr<int>)), Alignment> buffer_{};
AlignedBuffer<RealInlineBufferCapacity, Alignment> buffer_{};
/**
* Information about the type that is currently stored.
@ -144,7 +153,12 @@ class Any {
Any(const Any &other) : info_(other.info_)
{
if (info_ != nullptr) {
info_->copy_construct(&buffer_, &other.buffer_);
if (info_->copy_construct != nullptr) {
info_->copy_construct(&buffer_, &other.buffer_);
}
else {
memcpy(&buffer_, &other.buffer_, RealInlineBufferCapacity);
}
}
}
@ -155,7 +169,12 @@ class Any {
Any(Any &&other) noexcept : info_(other.info_)
{
if (info_ != nullptr) {
info_->move_construct(&buffer_, &other.buffer_);
if (info_->move_construct != nullptr) {
info_->move_construct(&buffer_, &other.buffer_);
}
else {
memcpy(&buffer_, &other.buffer_, RealInlineBufferCapacity);
}
}
}
@ -179,7 +198,9 @@ class Any {
~Any()
{
if (info_ != nullptr) {
info_->destruct(&buffer_);
if (info_->destruct != nullptr) {
info_->destruct(&buffer_);
}
}
}
@ -213,7 +234,9 @@ class Any {
void reset()
{
if (info_ != nullptr) {
info_->destruct(&buffer_);
if (info_->destruct != nullptr) {
info_->destruct(&buffer_);
}
}
info_ = nullptr;
}
@ -265,14 +288,20 @@ class Any {
void *get()
{
BLI_assert(info_ != nullptr);
return const_cast<void *>(info_->get(&buffer_));
if (info_->get != nullptr) {
return const_cast<void *>(info_->get(&buffer_));
}
return &buffer_;
}
/** Get a pointer to the stored value. */
const void *get() const
{
BLI_assert(info_ != nullptr);
return info_->get(&buffer_);
if (info_->get != nullptr) {
return info_->get(&buffer_);
}
return &buffer_;
}
/**

View File

@ -600,6 +600,8 @@ class GVArrayImpl_For_GSpan_final final : public GVArrayImpl_For_GSpan {
CommonVArrayInfo common_info() const override;
};
template<> inline constexpr bool is_trivial_extended_v<GVArrayImpl_For_GSpan_final> = true;
/** \} */
/* -------------------------------------------------------------------- */
@ -638,6 +640,9 @@ class GVArrayImpl_For_SingleValueRef_final final : public GVArrayImpl_For_Single
CommonVArrayInfo common_info() const override;
};
template<>
inline constexpr bool is_trivial_extended_v<GVArrayImpl_For_SingleValueRef_final> = true;
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -18,6 +18,21 @@
namespace blender {
/**
* Under some circumstances #std::is_trivial_v<T> is false even though we know that the type is
* actually trivial. Using that extra knowledge allows for some optimizations.
*/
template<typename T> inline constexpr bool is_trivial_extended_v = std::is_trivial_v<T>;
template<typename T>
inline constexpr bool is_trivially_destructible_extended_v = is_trivial_extended_v<T> ||
std::is_trivially_destructible_v<T>;
template<typename T>
inline constexpr bool is_trivially_copy_constructible_extended_v =
is_trivial_extended_v<T> || std::is_trivially_copy_constructible_v<T>;
template<typename T>
inline constexpr bool is_trivially_move_constructible_extended_v =
is_trivial_extended_v<T> || std::is_trivially_move_constructible_v<T>;
/**
* Call the destructor on n consecutive values. For trivially destructible types, this does
* nothing.
@ -38,7 +53,7 @@ template<typename T> void destruct_n(T *ptr, int64_t n)
/* This is not strictly necessary, because the loop below will be optimized away anyway. It is
* nice to make behavior this explicitly, though. */
if (std::is_trivially_destructible_v<T>) {
if (is_trivially_destructible_extended_v<T>) {
return;
}

View File

@ -319,6 +319,9 @@ template<typename T> class VArrayImpl_For_Span_final final : public VArrayImpl_F
}
};
template<typename T>
inline constexpr bool is_trivial_extended_v<VArrayImpl_For_Span_final<T>> = true;
/**
* A variant of `VArrayImpl_For_Span` that owns the underlying data.
* The `Container` type has to implement a `size()` and `data()` method.
@ -379,6 +382,9 @@ template<typename T> class VArrayImpl_For_Single final : public VArrayImpl<T> {
}
};
template<typename T>
inline constexpr bool is_trivial_extended_v<VArrayImpl_For_Single<T>> = is_trivial_extended_v<T>;
/**
* This class makes it easy to create a virtual array for an existing function or lambda. The
* `GetFunc` should take a single `index` argument and return the value at that index.
@ -522,6 +528,13 @@ class VArrayImpl_For_DerivedSpan final : public VMutableArrayImpl<ElemT> {
}
};
template<typename StructT,
typename ElemT,
ElemT (*GetFunc)(const StructT &),
void (*SetFunc)(StructT &, ElemT)>
inline constexpr bool
is_trivial_extended_v<VArrayImpl_For_DerivedSpan<StructT, ElemT, GetFunc, SetFunc>> = true;
namespace detail {
/**