From abe8cecfca5aeefce621164533b22fd1594a050b Mon Sep 17 00:00:00 2001 From: isidorostsa Date: Tue, 14 Nov 2023 10:48:45 +0200 Subject: [PATCH 1/4] uninitialized_relocate_backward docs --- .../algorithms/uninitialized_relocate.hpp | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp index 3be0d25038f0..f44ec49a063b 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp @@ -63,7 +63,7 @@ namespace hpx { template FwdIter uninitialized_relocate(InIter1 first, InIter2 last, FwdIter dest); - /// Relocates the elements in the range defined by [first, first + count), to an + /// Relocates the elements in the range defined by [first, last), to an /// uninitialized memory area beginning at \a dest. If an exception is /// thrown during the move-construction of an element, all elements left /// in the input range, as well as all objects already constructed in the @@ -135,6 +135,125 @@ namespace hpx { uninitialized_relocate( ExPolicy&& policy, InIter1 first, InIter2 last, FwdIter dest); + /// Relocates the elements in the range, defined by [first, last), to an + /// uninitialized memory area ending at \a dest_last. The objects are + /// processed in reverse order. If an exception is thrown during the + /// the move-construction of an element, all elements left in the + /// input range, as well as all objects already constructed in the + /// destination range are destroyed. After this algorithm completes, the + /// source range should be freed or reused without destroying the objects. + /// + /// \note Complexity: time: O(n), space: O(1) + /// 1) For "trivially relocatable" underlying types (T) and + /// a contiguous iterator range [first, last): + /// std::distance(first, last)*sizeof(T) bytes are copied. + /// 2) For "trivially relocatable" underlying types (T) and + /// a non-contiguous iterator range [first, last): + /// std::distance(first, last) memory copies of sizeof(T) + /// bytes each are performed. + /// 3) For "non-trivially relocatable" underlying types (T): + /// std::distance(first, last) move assignments and + /// destructions are performed. + /// + /// \note Declare a type as "trivially relocatable" using the + /// `HPX_DECLARE_TRIVIALLY_RELOCATABLE` macros found in + /// . + /// + /// \tparam BiIter1 The type of the source range (deduced). + /// This iterator type must meet the requirements of a + /// Bidirectional iterator. + /// \tparam BiIter2 The type of the iterator representing the + /// destination range (deduced). + /// This iterator type must meet the requirements of a + /// Bidirectional iterator. + /// + /// \param first Refers to the beginning of the sequence of elements + /// the algorithm will be applied to. + /// \param last Refers to the end of the sequence of elements the + /// algorithm will be applied to. + /// \param dest_last Refers to the beginning of the destination range. + /// + /// The assignments in the parallel \a uninitialized_relocate algorithm invoked + /// without an execution policy object will execute in sequential order in + /// the calling thread. + /// + /// \returns The \a uninitialized_relocate_backward algorithm returns \a BiIter2. + /// The \a uninitialized_relocate_backward algorithm returns the + /// bidirectional iterator to the first element in the destination range. + /// + template + BiIter2 uninitialized_relocate_backward(BiIter1 first, BiIter1 last, + BiIter2 dest_last) + + /// Relocates the elements in the range, defined by [first, last), to an + /// uninitialized memory area ending at \a dest_last. The order of the + /// relocation of the objects depends on the execution policy. If an + /// exception is thrown during the the move-construction of an element, + /// all elements left in the input range, as well as all objects already + /// constructed in the destination range are destroyed. After this algorithm + /// completes, the source range should be freed or reused without destroying + /// the objects. + /// + /// \note Using the \a uninitialized_relocate_backward algorithm with the + /// with a non-sequenced execution policy, will not guarantee the + /// order of the relocation of the objects. + /// + /// \note Complexity: time: O(n), space: O(1) + /// 1) For "trivially relocatable" underlying types (T) and + /// a contiguous iterator range [first, last): + /// std::distance(first, last)*sizeof(T) bytes are copied. + /// 2) For "trivially relocatable" underlying types (T) and + /// a non-contiguous iterator range [first, last): + /// std::distance(first, last) memory copies of sizeof(T) + /// bytes each are performed. + /// 3) For "non-trivially relocatable" underlying types (T): + /// std::distance(first, last) move assignments and + /// destructions are performed. + /// + /// \note Declare a type as "trivially relocatable" using the + /// `HPX_DECLARE_TRIVIALLY_RELOCATABLE` macros found in + /// . + /// + /// \tparam ExPolicy The type of the execution policy to use (deduced). + /// It describes the manner in which the execution + /// of the algorithm may be parallelized and the manner + /// in which it executes the assignments. + /// \tparam BiIter1 The type of the source range (deduced). + /// This iterator type must meet the requirements of a + /// Bidirectional iterator. + /// \tparam BiIter2 The type of the iterator representing the + /// destination range (deduced). + /// This iterator type must meet the requirements of a + /// Bidirectional iterator. + /// + /// \param policy The execution policy to use for the scheduling of + /// the iterations. + /// \param first Refers to the beginning of the sequence of elements + /// the algorithm will be applied to. + /// \param last Refers to the end of the sequence of elements the + /// algorithm will be applied to. + /// \param dest_last Refers to the end of the destination range. + /// + /// The assignments in the parallel \a uninitialized_relocate_backward algorithm invoked + /// with an execution policy object of type \a parallel_policy or + /// \a parallel_task_policy are permitted to execute in an + /// unordered fashion in unspecified threads, and indeterminately sequenced + /// within each thread. + /// + /// \returns The \a uninitialized_relocate_backward algorithm returns a + /// \a hpx::future, if the execution policy is of type + /// \a sequenced_task_policy or + /// \a parallel_task_policy and + /// returns \a BiIter2 otherwise. + /// The \a uninitialized_relocate_backward algorithm returns the + /// bidirectional iterator to the first element in the destination + /// range. + /// + template + hpx::parallel::util::detail::algorithm_result + uninitialized_relocate_backward( + ExPolicy&& policy, BiIter1 first, BiIter1 last, BiIter2 dest_last) + /// Relocates the elements in the range, defined by [first, last), to an /// uninitialized memory area beginning at \a dest. If an exception is /// thrown during the move-construction of an element, all elements left From 74118e3d574976ad6b3acc924ec401d3e1a8b706 Mon Sep 17 00:00:00 2001 From: isidorostsa Date: Tue, 14 Nov 2023 16:26:18 +0200 Subject: [PATCH 2/4] relocation in small_vector --- .../datastructures/detail/small_vector.hpp | 103 +++++++++++------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp index ea7e69202f58..90265f597f0d 100644 --- a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp +++ b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include @@ -294,20 +296,6 @@ namespace hpx::detail { reinterpret_cast(m_data.data() + std::alignment_of_v)); } - static void uninitialized_move_and_destroy( - T* source_ptr, T* target_ptr, std::size_t size) noexcept - { - if constexpr (std::is_trivially_copyable_v) - { - std::memcpy(target_ptr, source_ptr, size * sizeof(T)); - } - else - { - std::uninitialized_move_n(source_ptr, size, target_ptr); - std::destroy_n(source_ptr, size); - } - } - void realloc(std::size_t new_capacity) { if (new_capacity <= N) @@ -319,8 +307,9 @@ namespace hpx::detail { { // indirect -> direct auto* storage = indirect(); - uninitialized_move_and_destroy( - storage->data(), direct_data(), storage->size()); + ::hpx::experimental::util:: + uninitialized_relocate_n_primitive( + storage->data(), storage->size(), direct_data()); set_direct_and_size(storage->size()); detail::storage::dealloc(storage); } @@ -332,15 +321,19 @@ namespace hpx::detail { if (is_direct()) { // direct -> indirect - uninitialized_move_and_destroy(data(), - storage->data(), size()); + ::hpx::experimental::util:: + uninitialized_relocate_n_primitive( + data(), + size(), storage->data()); storage->size(size()); } else { // indirect -> indirect - uninitialized_move_and_destroy(data(), - storage->data(), size()); + ::hpx::experimental::util:: + uninitialized_relocate_n_primitive( + data(), + size(), storage->data()); storage->size(size()); detail::storage::dealloc(indirect()); } @@ -501,9 +494,12 @@ namespace hpx::detail { auto* const erase_end = (std::min)(const_cast(to), container_end); - std::move(erase_end, container_end, erase_begin); + // forward relocation + ::hpx::experimental::util::uninitialized_relocate_primitive( + erase_end, container_end, erase_begin); + auto const num_erased = std::distance(erase_begin, erase_end); - std::destroy(container_end - num_erased, container_end); + set_size(size() - num_erased); return erase_begin; } @@ -547,9 +543,8 @@ namespace hpx::detail { auto s = other.size(); auto* other_end = other_ptr + s; - std::uninitialized_move( + ::hpx::experimental::util::uninitialized_relocate_primitive( other_ptr, other_end, data()); - std::destroy(other_ptr, other_end); set_size(s); } other.set_direct_and_size(0); @@ -563,24 +558,45 @@ namespace hpx::detail { // * source_begin <= target_begin // * source_end onwards is uninitialized memory // - // Destroys then empty elements in [source_begin, source_end) + // Destroys the empty elements in [source_begin, source_end) auto shift_right( T* source_begin, T* source_end, T* target_begin) noexcept { // 1. uninitialized moves auto const num_moves = std::distance(source_begin, source_end); auto const target_end = target_begin + num_moves; - auto const num_uninitialized_move = - (std::min)(num_moves, std::distance(source_end, target_end)); - std::uninitialized_move(source_end - num_uninitialized_move, - source_end, target_end - num_uninitialized_move); - std::move_backward(source_begin, - source_end - num_uninitialized_move, - target_end - num_uninitialized_move); - std::destroy(source_begin, (std::min)(source_end, target_begin)); + + ::hpx::experimental::util::uninitialized_relocate_backward_primitive( + source_begin, source_end, target_end); + + + // In the following commented code we split the move into an + // uninitialized move and a move, using relocation only when it is + // a memcpy. The thought process was that a move assignment might + // be faster than a move construction in certain cases, but since + // this is likely not the usual case we always choose relocation. + +// if constexpr (hpx::experimental::is_trivially_relocatable_v) +// { +// ::hpx::experimental::util::uninitialized_relocate_backward_primitive( +// source_begin, source_end, target_end); +// } +// else +// { +// auto const num_uninitialized_move = (std::min)( +// num_moves, std::distance(source_end, target_end)); +// +// std::uninitialized_move(source_end - num_uninitialized_move, +// source_end, target_end - num_uninitialized_move); +// std::move_backward(source_begin, +// source_end - num_uninitialized_move, +// target_end - num_uninitialized_move); +// std::destroy( +// source_begin, (std::min)(source_end, target_begin)); +// } } - // makes space for uninitialized data of cout elements. Also updates + // makes space for uninitialized data of count elements. Also updates // size. template [[nodiscard]] auto make_uninitialized_space_new( @@ -592,14 +608,25 @@ namespace hpx::detail { target.reserve(s + count); // move everything [begin, pos[ - auto* target_pos = std::uninitialized_move( - data(), p, target.template data()); + auto* target_pos = + std::get<1>(::hpx::experimental::util::uninitialized_relocate_primitive( + data(), p, target.template data())); // move everything [pos, end] - std::uninitialized_move(p, data() + s, target_pos + count); + ::hpx::experimental::util::uninitialized_relocate_primitive( + p, data() + s, target_pos + count); target.template set_size(s + count); - *this = HPX_MOVE(target); + + // objects are already destroyed from the relocation + if (!is_direct()) + { + detail::storage::dealloc(indirect()); + } + set_direct_and_size(0); + + do_move_assign(HPX_MOVE(target)); + return target_pos; } From c8cd53a53819d46dc5b6b9fe5cfb2daa513b7f4d Mon Sep 17 00:00:00 2001 From: isidorostsa Date: Wed, 27 Dec 2023 13:36:46 +0200 Subject: [PATCH 3/4] option to emulate inplace_vector --- .../datastructures/detail/small_vector.hpp | 124 ++++++++++++++++-- 1 file changed, 113 insertions(+), 11 deletions(-) diff --git a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp index 90265f597f0d..50e306376f4c 100644 --- a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp +++ b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -220,7 +221,7 @@ namespace hpx::detail { // note: Allocator is currently unused template > + typename Allocator = std::allocator, bool emulate_inplace_vector = false> class small_vector { static_assert(MinInlineCapacity <= 127, @@ -245,13 +246,13 @@ namespace hpx::detail { // m_data[0] & 1: lowest bit is 0 for indirect mode // m_data[0..7]: stores an uintptr_t, which points to the indirect // data. - alignas(alignment_of_small_vector()) std::array(MinInlineCapacity)> m_data; [[nodiscard]] constexpr auto is_direct() const noexcept -> bool { - return (m_data[0] & 1U) != 0U; + if constexpr (emulate_inplace_vector) return true; + else return (m_data[0] & 1U) != 0U; } [[nodiscard]] auto indirect() noexcept -> storage* @@ -298,6 +299,7 @@ namespace hpx::detail { void realloc(std::size_t new_capacity) { + static_assert(!emulate_inplace_vector, "If called in an inplace_vector, it is a bug."); if (new_capacity <= N) { // put everything into direct storage @@ -521,9 +523,17 @@ namespace hpx::detail { template void assign(It first, It last, std::forward_iterator_tag /*unused*/) { + auto s = std::distance(first, last); + if constexpr (emulate_inplace_vector) + { + // Can not have an inplace_vector with a size larger than N + if (s <= capacity()) { + throw std::bad_alloc(); + } + } + clear(); - auto s = std::distance(first, last); reserve(s); std::uninitialized_copy(first, last, data()); set_size(s); @@ -532,6 +542,8 @@ namespace hpx::detail { // precondition: all uninitialized void do_move_assign(small_vector&& other) noexcept { + // We assume that the template parameters of "other" are the same + // as "this". if (!other.is_direct()) { // take other's memory, even when empty @@ -684,7 +696,7 @@ namespace hpx::detail { set_direct_and_size(0); } - // performs a const_cast so we don't need this implementation twice + // performs a const_cast, so we don't need this implementation twice template [[nodiscard]] auto at(std::size_t idx) const -> T& { @@ -723,12 +735,26 @@ namespace hpx::detail { std::size_t count, T const& value, Allocator const& = Allocator()) : small_vector() { + if constexpr (emulate_inplace_vector) + { + // Can not have an inplace_vector with a size larger than N + if (count > N) { + throw std::bad_alloc(); + } + } resize(count, value); } explicit small_vector(std::size_t count, Allocator const& = Allocator()) : small_vector() { + if constexpr (emulate_inplace_vector) + { + // Can not have an inplace_vector with a size larger than N + if (count > N) { + throw std::bad_alloc(); + } + } reserve(count); if (is_direct()) { @@ -754,7 +780,14 @@ namespace hpx::detail { : small_vector() { auto s = other.size(); - reserve(s); + // If both vectors are direct with the same capacity it will fit + // without further allocations needed. + + if constexpr (!emulate_inplace_vector) + { + reserve(s); + } + std::uninitialized_copy(other.begin(), other.end(), begin()); set_size(s); } @@ -812,7 +845,7 @@ namespace hpx::detail { if (&other == this) { // It doesn't seem to be required to do self-check, but let's do - // it anyways to be safe + // it anyway to be safe return *this; } @@ -829,6 +862,13 @@ namespace hpx::detail { void resize(std::size_t count) { + if constexpr (emulate_inplace_vector) { + // Static vector cannot be resized beyond capacity + if (count > N) { + throw std::bad_alloc(); + } + } + if (count > capacity()) { reserve(count); @@ -846,6 +886,13 @@ namespace hpx::detail { void resize(std::size_t count, value_type const& value) { + if constexpr (emulate_inplace_vector) { + // Static vector cannot resize beyond capacity + if (count > N) { + throw std::bad_alloc(); + } + } + if (count > capacity()) { reserve(count); @@ -863,6 +910,12 @@ namespace hpx::detail { auto reserve(std::size_t s) { + if constexpr (emulate_inplace_vector) { + // Static vector cannot reserve beyond capacity + if (s > N) { + throw std::bad_alloc(); + } + } auto const old_capacity = capacity(); auto const new_capacity = calculate_new_capacity(s, old_capacity); if (new_capacity > old_capacity) @@ -873,6 +926,9 @@ namespace hpx::detail { [[nodiscard]] constexpr auto capacity() const noexcept -> std::size_t { + if constexpr (emulate_inplace_vector) { + return capacity(); + } if (is_direct()) { return capacity(); @@ -911,7 +967,15 @@ namespace hpx::detail { if (is_direct()) { s = direct_size(); - if (s < N) + if constexpr (emulate_inplace_vector) { + // Exceeded static_storage + if (s + 1 > N) { + throw std::bad_alloc(); + } + } + + // avoid double-checking for inplace_vector + if (emulate_inplace_vector || s < N) { set_direct_and_size(s + 1); return *hpx::construct_at( @@ -1110,19 +1174,29 @@ namespace hpx::detail { [[nodiscard]] static constexpr auto max_size() -> std::size_t { + if constexpr (emulate_inplace_vector) return N; return (std::numeric_limits::max)(); } void swap(small_vector& other) noexcept { // TODO we could try to do the minimum number of moves - std::swap(*this, other); + alignas(small_vector) std::byte buf[sizeof(small_vector)]; + hpx::experimental::relocate_at(&other, reinterpret_cast(buf)); + hpx::experimental::relocate_at(this, &other); + hpx::experimental::relocate_at(reinterpret_cast(buf), this); } void shrink_to_fit() { // per the standard we wouldn't need to do anything here. But since // we are so nice, let's do the shrink. + + if constexpr (emulate_inplace_vector) { + // Can not change the capacity of a static vector so noop + return; + } + auto const c = capacity(); auto const s = size(); if (s >= c) @@ -1130,7 +1204,7 @@ namespace hpx::detail { return; } - auto new_capacity = calculate_new_capacity(s, N); + auto new_capacity = calculate_new_capacity(s, static_capacity); if (new_capacity == c) { // nothing change! @@ -1143,6 +1217,12 @@ namespace hpx::detail { template auto emplace(const_iterator pos, Args&&... args) -> iterator { + if constexpr(emulate_inplace_vector) { + // it will be expanded by one element + if (direct_size() + 1 > N) { + throw std::bad_alloc(); + } + } auto* p = make_uninitialized_space(pos, 1); return hpx::construct_at( static_cast(p), HPX_FORWARD(Args, args)...); @@ -1161,6 +1241,11 @@ namespace hpx::detail { auto insert(const_iterator pos, std::size_t count, T const& value) -> iterator { + if constexpr(emulate_inplace_vector) { + if (direct_size() + count > N) { + throw std::bad_alloc(); + } + } auto* p = make_uninitialized_space(pos, count); std::uninitialized_fill_n(p, count, value); return p; @@ -1183,6 +1268,8 @@ namespace hpx::detail { auto s = size(); while (first != last) { + // if we are emulating inplace_vector, the out-of-bounds + // emplacement is caught in emplace_back emplace_back(*first); ++first; } @@ -1198,7 +1285,13 @@ namespace hpx::detail { auto insert(const_iterator pos, It first, It last, std::forward_iterator_tag /*unused*/) { - auto* p = make_uninitialized_space(pos, std::distance(first, last)); + auto d = std::distance(first, last); + if constexpr(emulate_inplace_vector) { + if (direct_size() + d > N) { + throw std::bad_alloc(); + } + } + auto* p = make_uninitialized_space(pos, d); std::uninitialized_copy(first, last, p); return p; } @@ -1275,6 +1368,9 @@ namespace hpx::detail { { return !(a > b); } + + template > + using inplace_vector = small_vector; } // namespace hpx::detail // NOLINTNEXTLINE(cert-dcl58-cpp) @@ -1300,3 +1396,9 @@ namespace std { //-V1061 return num_removed; } } // namespace std + +namespace hpx::experimental { + template + struct is_trivially_relocatable> : + is_trivially_relocatable {}; +} \ No newline at end of file From 433a86d54d34b017eb536fa3ca4f619d29e907c2 Mon Sep 17 00:00:00 2001 From: isidorostsa Date: Wed, 3 Apr 2024 16:59:13 -0500 Subject: [PATCH 4/4] Clang-format + Copyright --- .../datastructures/detail/small_vector.hpp | 156 +++++++++++------- 1 file changed, 95 insertions(+), 61 deletions(-) diff --git a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp index 50e306376f4c..7f0ca34d6f2e 100644 --- a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp +++ b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp @@ -1,4 +1,5 @@ // Copyright (c) 2022 Hartmut Kaiser +// Copyright (c) 2024 Isidoros Tsaousis-Seiras // // SPDX-License-Identifier: BSL-1.0 // Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -33,10 +34,10 @@ #include #include +#include #include #include #include -#include #include #include @@ -221,7 +222,8 @@ namespace hpx::detail { // note: Allocator is currently unused template , bool emulate_inplace_vector = false> + typename Allocator = std::allocator, + bool emulate_inplace_vector = false> class small_vector { static_assert(MinInlineCapacity <= 127, @@ -251,8 +253,10 @@ namespace hpx::detail { [[nodiscard]] constexpr auto is_direct() const noexcept -> bool { - if constexpr (emulate_inplace_vector) return true; - else return (m_data[0] & 1U) != 0U; + if constexpr (emulate_inplace_vector) + return true; + else + return (m_data[0] & 1U) != 0U; } [[nodiscard]] auto indirect() noexcept -> storage* @@ -299,7 +303,8 @@ namespace hpx::detail { void realloc(std::size_t new_capacity) { - static_assert(!emulate_inplace_vector, "If called in an inplace_vector, it is a bug."); + static_assert(!emulate_inplace_vector, + "If called in an inplace_vector, it is a bug."); if (new_capacity <= N) { // put everything into direct storage @@ -527,7 +532,8 @@ namespace hpx::detail { if constexpr (emulate_inplace_vector) { // Can not have an inplace_vector with a size larger than N - if (s <= capacity()) { + if (s <= capacity()) + { throw std::bad_alloc(); } } @@ -578,34 +584,37 @@ namespace hpx::detail { auto const num_moves = std::distance(source_begin, source_end); auto const target_end = target_begin + num_moves; - ::hpx::experimental::util::uninitialized_relocate_backward_primitive( - source_begin, source_end, target_end); - - - // In the following commented code we split the move into an - // uninitialized move and a move, using relocation only when it is - // a memcpy. The thought process was that a move assignment might - // be faster than a move construction in certain cases, but since - // this is likely not the usual case we always choose relocation. - -// if constexpr (hpx::experimental::is_trivially_relocatable_v) -// { -// ::hpx::experimental::util::uninitialized_relocate_backward_primitive( -// source_begin, source_end, target_end); -// } -// else -// { -// auto const num_uninitialized_move = (std::min)( -// num_moves, std::distance(source_end, target_end)); -// -// std::uninitialized_move(source_end - num_uninitialized_move, -// source_end, target_end - num_uninitialized_move); -// std::move_backward(source_begin, -// source_end - num_uninitialized_move, -// target_end - num_uninitialized_move); -// std::destroy( -// source_begin, (std::min)(source_end, target_begin)); -// } + ::hpx::experimental::util:: + uninitialized_relocate_backward_primitive( + source_begin, source_end, target_end); + + /* + In the following commented code we split the move into an + uninitialized move and a move, using relocation only when it is + a memcpy. The thought process was that a move assignment might + be faster than a move construction in certain cases, but since + this is likely not the usual case we always choose relocation. + + if constexpr (hpx::experimental::is_trivially_relocatable_v) + { + ::hpx::experimental::util:: + uninitialized_relocate_backward_primitive( + source_begin, source_end, target_end); + } + else + { + auto const num_uninitialized_move = (std::min)( + num_moves, std::distance(source_end, target_end)); + + std::uninitialized_move(source_end - num_uninitialized_move, + source_end, target_end - num_uninitialized_move); + std::move_backward(source_begin, + source_end - num_uninitialized_move, + target_end - num_uninitialized_move); + std::destroy( + source_begin, (std::min)(source_end, target_begin)); + } + */ } // makes space for uninitialized data of count elements. Also updates @@ -620,8 +629,8 @@ namespace hpx::detail { target.reserve(s + count); // move everything [begin, pos[ - auto* target_pos = - std::get<1>(::hpx::experimental::util::uninitialized_relocate_primitive( + auto* target_pos = std::get<1>( + ::hpx::experimental::util::uninitialized_relocate_primitive( data(), p, target.template data())); // move everything [pos, end] @@ -738,7 +747,8 @@ namespace hpx::detail { if constexpr (emulate_inplace_vector) { // Can not have an inplace_vector with a size larger than N - if (count > N) { + if (count > N) + { throw std::bad_alloc(); } } @@ -751,7 +761,8 @@ namespace hpx::detail { if constexpr (emulate_inplace_vector) { // Can not have an inplace_vector with a size larger than N - if (count > N) { + if (count > N) + { throw std::bad_alloc(); } } @@ -862,9 +873,11 @@ namespace hpx::detail { void resize(std::size_t count) { - if constexpr (emulate_inplace_vector) { + if constexpr (emulate_inplace_vector) + { // Static vector cannot be resized beyond capacity - if (count > N) { + if (count > N) + { throw std::bad_alloc(); } } @@ -886,9 +899,11 @@ namespace hpx::detail { void resize(std::size_t count, value_type const& value) { - if constexpr (emulate_inplace_vector) { + if constexpr (emulate_inplace_vector) + { // Static vector cannot resize beyond capacity - if (count > N) { + if (count > N) + { throw std::bad_alloc(); } } @@ -910,9 +925,11 @@ namespace hpx::detail { auto reserve(std::size_t s) { - if constexpr (emulate_inplace_vector) { + if constexpr (emulate_inplace_vector) + { // Static vector cannot reserve beyond capacity - if (s > N) { + if (s > N) + { throw std::bad_alloc(); } } @@ -926,7 +943,8 @@ namespace hpx::detail { [[nodiscard]] constexpr auto capacity() const noexcept -> std::size_t { - if constexpr (emulate_inplace_vector) { + if constexpr (emulate_inplace_vector) + { return capacity(); } if (is_direct()) @@ -967,9 +985,11 @@ namespace hpx::detail { if (is_direct()) { s = direct_size(); - if constexpr (emulate_inplace_vector) { + if constexpr (emulate_inplace_vector) + { // Exceeded static_storage - if (s + 1 > N) { + if (s + 1 > N) + { throw std::bad_alloc(); } } @@ -1174,7 +1194,8 @@ namespace hpx::detail { [[nodiscard]] static constexpr auto max_size() -> std::size_t { - if constexpr (emulate_inplace_vector) return N; + if constexpr (emulate_inplace_vector) + return N; return (std::numeric_limits::max)(); } @@ -1182,9 +1203,11 @@ namespace hpx::detail { { // TODO we could try to do the minimum number of moves alignas(small_vector) std::byte buf[sizeof(small_vector)]; - hpx::experimental::relocate_at(&other, reinterpret_cast(buf)); + hpx::experimental::relocate_at( + &other, reinterpret_cast(buf)); hpx::experimental::relocate_at(this, &other); - hpx::experimental::relocate_at(reinterpret_cast(buf), this); + hpx::experimental::relocate_at( + reinterpret_cast(buf), this); } void shrink_to_fit() @@ -1192,7 +1215,8 @@ namespace hpx::detail { // per the standard we wouldn't need to do anything here. But since // we are so nice, let's do the shrink. - if constexpr (emulate_inplace_vector) { + if constexpr (emulate_inplace_vector) + { // Can not change the capacity of a static vector so noop return; } @@ -1217,9 +1241,11 @@ namespace hpx::detail { template auto emplace(const_iterator pos, Args&&... args) -> iterator { - if constexpr(emulate_inplace_vector) { + if constexpr (emulate_inplace_vector) + { // it will be expanded by one element - if (direct_size() + 1 > N) { + if (direct_size() + 1 > N) + { throw std::bad_alloc(); } } @@ -1241,8 +1267,10 @@ namespace hpx::detail { auto insert(const_iterator pos, std::size_t count, T const& value) -> iterator { - if constexpr(emulate_inplace_vector) { - if (direct_size() + count > N) { + if constexpr (emulate_inplace_vector) + { + if (direct_size() + count > N) + { throw std::bad_alloc(); } } @@ -1286,8 +1314,10 @@ namespace hpx::detail { std::forward_iterator_tag /*unused*/) { auto d = std::distance(first, last); - if constexpr(emulate_inplace_vector) { - if (direct_size() + d > N) { + if constexpr (emulate_inplace_vector) + { + if (direct_size() + d > N) + { throw std::bad_alloc(); } } @@ -1369,7 +1399,8 @@ namespace hpx::detail { return !(a > b); } - template > + template > using inplace_vector = small_vector; } // namespace hpx::detail @@ -1399,6 +1430,9 @@ namespace std { //-V1061 namespace hpx::experimental { template - struct is_trivially_relocatable> : - is_trivially_relocatable {}; -} \ No newline at end of file + struct is_trivially_relocatable< + hpx::detail::small_vector> + : is_trivially_relocatable + { + }; +} // namespace hpx::experimental