From 4af1c66ba66dbf57f6fd38597d6435ab4b2e35b1 Mon Sep 17 00:00:00 2001 From: KRM7 <70973547+KRM7@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:43:50 +0100 Subject: [PATCH] insert/emplace implementation improvements --- CMakeLists.txt | 2 +- benchmark/small_vector.cpp | 29 ++++-- src/small_vector.hpp | 186 +++++++++++++++++++++++++------------ 3 files changed, 149 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f0709f..780b163 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ endif() if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -permissive- -W4 -WX -diagnostics:caret") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror -pedantic-errors -g") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Wconversion -Werror -pedantic-errors -g") endif() if(CMAKE_BUILD_TYPE MATCHES "(Release|RelWithDebInfo)") diff --git a/benchmark/small_vector.cpp b/benchmark/small_vector.cpp index 5f1a191..b926a88 100644 --- a/benchmark/small_vector.cpp +++ b/benchmark/small_vector.cpp @@ -11,10 +11,11 @@ inline constexpr size_t LARGE_SIZE = 100; template void benchmark_construct_from_size(benchmark::State& state) { - const size_t size = state.range(0); + size_t size = state.range(0); for (auto _ : state) { + benchmark::DoNotOptimize(size); V vec(size); benchmark::DoNotOptimize(vec); benchmark::ClobberMemory(); @@ -29,11 +30,14 @@ BENCHMARK(benchmark_construct_from_size>)->ArgName("size")->Ar template void benchmark_construct_from_size_value(benchmark::State& state) { - const size_t size = state.range(0); + size_t size = state.range(0); + int value = 2; for (auto _ : state) { - V vec(size, 2); + benchmark::DoNotOptimize(size); + benchmark::DoNotOptimize(value); + V vec(size, value); benchmark::DoNotOptimize(vec); benchmark::ClobberMemory(); } @@ -47,10 +51,11 @@ BENCHMARK(benchmark_construct_from_size_value>)->ArgName("size template void benchmark_construct_from_range(benchmark::State& state) { - const V src(state.range(0), 0); + V src(state.range(0), 0); for (auto _ : state) { + benchmark::DoNotOptimize(src); V vec(src.begin(), src.end()); benchmark::DoNotOptimize(vec); benchmark::ClobberMemory(); @@ -72,6 +77,8 @@ void benchmark_assign_reserved(benchmark::State& state) for (auto _ : state) { + benchmark::DoNotOptimize(src); + dst.clear(); benchmark::DoNotOptimize(dst); @@ -113,12 +120,14 @@ BENCHMARK(benchmark_swap>)->ArgName("size")->Arg(SMALL_SIZE)-> template void benchmark_resize(benchmark::State& state) { - const size_t size = state.range(0); + size_t size = state.range(0); V vec(size); for (auto _ : state) { + benchmark::DoNotOptimize(size); + vec.resize(0); benchmark::DoNotOptimize(vec); @@ -236,13 +245,16 @@ void benchmark_insert_range_reserved(benchmark::State& state) const size_t size = state.range(0); V vec(size + 3, 1); + V rng = { 1, 2, 3 }; for (auto _ : state) { + benchmark::DoNotOptimize(rng); + vec.resize(size); benchmark::DoNotOptimize(vec); - vec.insert(vec.begin(), { 1, 2, 3 }); + vec.insert(vec.begin(), rng.begin(), rng.end()); benchmark::DoNotOptimize(vec); benchmark::ClobberMemory(); @@ -260,14 +272,17 @@ void benchmark_insert_range_reallocate(benchmark::State& state) const size_t final_size = state.range(0); const size_t start_size = small_vector::inline_capacity(); + V rng = { 1, 2, 3 }; + for (auto _ : state) { V vec(start_size); while (vec.size() < final_size) { + benchmark::DoNotOptimize(rng); benchmark::DoNotOptimize(vec); - vec.insert(vec.begin(), { 1, 2, 3 }); + vec.insert(vec.begin(), rng.begin(), rng.end()); benchmark::ClobberMemory(); } } diff --git a/src/small_vector.hpp b/src/small_vector.hpp index f807b7b..14a3956 100644 --- a/src/small_vector.hpp +++ b/src/small_vector.hpp @@ -322,11 +322,7 @@ namespace detail constexpr const T& operator*() const noexcept { return data_; } private: - union - { - unsigned char dummy_ = {}; - T data_; - }; + union { T data_; }; Allocator& alloc_; }; @@ -713,6 +709,7 @@ class small_vector constexpr bool empty() const noexcept { return first_ == last_; } constexpr size_type size() const noexcept { return size_type(last_ - first_); } + constexpr difference_type ssize() const noexcept { return last_ - first_; } constexpr size_type capacity() const noexcept { return size_type(last_alloc_ - first_); } constexpr size_type max_size() const noexcept { return std::allocator_traits::max_size(alloc_); } @@ -793,9 +790,7 @@ class small_vector constexpr reference emplace_back(Args&&... args) { if (last_ != last_alloc_) return emplace_back_unchecked(std::forward(args)...); - - reallocate_append(next_capacity(), std::forward(args)...); - return back(); + return *reallocate_append(next_capacity(), std::forward(args)...); } template @@ -818,64 +813,73 @@ class small_vector template constexpr iterator emplace(const_iterator pos, Args&&... args) { - if (pos == cend()) return std::addressof(emplace_back(std::forward(args)...)); + if (size() != capacity()) + { + if (pos == cend()) return std::addressof(emplace_back_unchecked(std::forward(args)...)); - detail::allocator_managed new_elem(alloc_, std::forward(args)...); + detail::allocator_managed new_elem(alloc_, std::forward(args)...); - const auto offset = std::distance(cbegin(), pos); + const difference_type offset = std::distance(cbegin(), pos); - if (size() == capacity()) reallocate_n(next_capacity()); - detail::construct(alloc_, last_, std::move(back())); - std::shift_right(first_ + offset, last_++, 1); - *(first_ + offset) = std::move(*new_elem); - return first_ + offset; + detail::construct(alloc_, last_, std::move(back())); + std::shift_right(first_ + offset, last_++, 1); + *(first_ + offset) = std::move(*new_elem); + return first_ + offset; + } + + return reallocate_emplace(next_capacity(), pos, std::forward(args)...); } constexpr iterator insert(const_iterator pos, size_type count, const T& value) { - const auto offset = std::distance(cbegin(), pos); - const auto src_size = static_cast(count); - const auto new_size = size() + count; - - reserve(new_size); - - const auto middle = std::max(last_ - src_size, first_ + offset); - const auto moved_size = last_ - middle; - const auto new_last = last_ + src_size; - const auto new_middle = last_ + src_size - moved_size; - const auto old_last = last_; - - detail::construct_range(alloc_, last_, new_middle, value); - last_ = new_middle; - detail::relocate_range_weak(alloc_, middle, old_last, new_middle); - last_ = new_last; - detail::assign_range(middle, old_last, value); + if (capacity() - size() >= count) + { + const difference_type offset = std::distance(cbegin(), pos); + const difference_type src_size = difference_type(count); + + const auto middle = first_ + std::max(ssize() - src_size, offset); + const auto moved_size = last_ - middle; + const auto old_last = last_; + const auto new_last = last_ + src_size; + const auto new_middle = middle + src_size; + + detail::construct_range(alloc_, last_, new_middle, value); + last_ = new_middle; + detail::relocate_range_weak(alloc_, middle, old_last, new_middle); + last_ = new_last; + detail::assign_range(middle, old_last, value); + + return first_ + offset; + } - return first_ + offset; + return reallocate_insert(next_capacity(count), pos, count, value); } template constexpr iterator insert(const_iterator pos, Iter src_first, Iter src_last) { - const auto offset = std::distance(cbegin(), pos); - const auto src_size = std::distance(src_first, src_last); - const auto new_size = size() + src_size; + const difference_type src_size = std::distance(src_first, src_last); - reserve(new_size); + if (capacity() - size() >= size_type(src_size)) + { + const difference_type offset = std::distance(cbegin(), pos); - const auto middle = std::max(last_ - src_size, first_ + offset); - const auto moved_size = last_ - middle; - const auto new_last = last_ + src_size; - const auto new_middle = last_ + src_size - moved_size; - const auto old_last = last_; + const auto middle = first_ + std::max(ssize() - src_size, offset); + const auto moved_size = last_ - middle; + const auto old_last = last_; + const auto new_last = last_ + src_size; + const auto new_middle = middle + src_size; - detail::construct_range(alloc_, last_, new_middle, src_first + moved_size); - last_ = new_middle; - detail::relocate_range_weak(alloc_, middle, old_last, new_middle); - last_ = new_last; - detail::assign_range(middle, old_last, src_first); + detail::construct_range(alloc_, last_, new_middle, std::next(src_first, moved_size)); + last_ = new_middle; + detail::relocate_range_weak(alloc_, middle, old_last, new_middle); + last_ = new_last; + detail::assign_range(middle, old_last, src_first); - return first_ + offset; + return first_ + offset; + } + + return reallocate_insert(next_capacity(size_type(src_size)), pos, src_first, src_last); } template @@ -929,16 +933,12 @@ class small_vector constexpr void allocate_n(size_type count) { - if (count <= buffer_.size()) - { - set_buffer_storage(0); - } - else - { - detail::alloc_result_t alloc_result = detail::allocate(alloc_, count); - first_ = alloc_result.data; - last_alloc_ = alloc_result.data + alloc_result.size; - } + if (count <= inline_capacity()) return set_buffer_storage(0); + + detail::alloc_result_t alloc_result = detail::allocate(alloc_, count); + first_ = alloc_result.data; + last_ = alloc_result.data; + last_alloc_ = alloc_result.data + alloc_result.size; } constexpr void reallocate_n(size_type new_capacity) @@ -955,7 +955,7 @@ class small_vector } template - constexpr void reallocate_append(size_type new_capacity, Args&&... args) + constexpr iterator reallocate_append(size_type new_capacity, Args&&... args) { const size_type old_size = size(); @@ -968,6 +968,72 @@ class small_vector detail::destroy_range(alloc_, first_, last_); deallocate(); set_storage(alloc_result.data, old_size + 1, alloc_result.size); + + return alloc_result.data + old_size; + } + + template + constexpr iterator reallocate_emplace(size_t new_capacity, const_iterator pos, Args&&... args) + { + const size_type old_size = size(); + const difference_type offset = std::distance(cbegin(), pos); + + detail::alloc_result_t alloc_result = detail::allocate(alloc_, new_capacity); + detail::scope_exit guard1{ [&] { detail::deallocate(alloc_, alloc_result.data, alloc_result.size); } }; + detail::construct(alloc_, alloc_result.data + offset, std::forward(args)...); + detail::scope_exit guard2{ [&] { detail::destroy(alloc_, alloc_result.data + offset); } }; + detail::relocate_range_strong(alloc_, first_, first_ + offset, alloc_result.data); + detail::scope_exit guard3{ [&] { detail::destroy_range(alloc_, alloc_result.data, alloc_result.data + offset); } }; + detail::relocate_range_strong(alloc_, first_ + offset, last_, alloc_result.data + offset + 1); + { guard1.release(); guard2.release(); guard3.release(); } + detail::destroy_range(alloc_, first_, last_); + deallocate(); + set_storage(alloc_result.data, old_size + 1, alloc_result.size); + + return alloc_result.data + offset; + } + + constexpr iterator reallocate_insert(size_t new_capacity, const_iterator pos, size_type count, const T& value) + { + const size_type old_size = size(); + const difference_type src_size = difference_type(count); + const difference_type offset = std::distance(cbegin(), pos); + + detail::alloc_result_t alloc_result = detail::allocate(alloc_, new_capacity); + detail::scope_exit guard1{ [&] { detail::deallocate(alloc_, alloc_result.data, alloc_result.size); } }; + detail::relocate_range_weak(alloc_, first_, first_ + offset, alloc_result.data); + detail::scope_exit guard2{ [&] { detail::destroy_range(alloc_, alloc_result.data, alloc_result.data + offset); } }; + detail::construct_range(alloc_, alloc_result.data + offset, alloc_result.data + offset + src_size, value); + detail::scope_exit guard3{ [&] { detail::destroy_range(alloc_, alloc_result.data + offset, alloc_result.data + offset + src_size); } }; + detail::relocate_range_weak(alloc_, first_ + offset, last_, alloc_result.data + offset + src_size); + { guard1.release(); guard2.release(); guard3.release(); } + detail::destroy_range(alloc_, first_, last_); + deallocate(); + set_storage(alloc_result.data, old_size + count, alloc_result.size); + + return alloc_result.data + offset; + } + + template + constexpr iterator reallocate_insert(size_t new_capacity, const_iterator pos, Iter src_first, Iter src_last) + { + const size_type old_size = size(); + const difference_type src_size = std::distance(src_first, src_last); + const difference_type offset = std::distance(cbegin(), pos); + + detail::alloc_result_t alloc_result = detail::allocate(alloc_, new_capacity); + detail::scope_exit guard1{ [&] { detail::deallocate(alloc_, alloc_result.data, alloc_result.size); } }; + detail::relocate_range_weak(alloc_, first_, first_ + offset, alloc_result.data); + detail::scope_exit guard2{ [&] { detail::destroy_range(alloc_, alloc_result.data, alloc_result.data + offset); } }; + detail::construct_range(alloc_, alloc_result.data + offset, alloc_result.data + offset + src_size, src_first); + detail::scope_exit guard3{ [&] { detail::destroy_range(alloc_, alloc_result.data + offset, alloc_result.data + offset + src_size); } }; + detail::relocate_range_weak(alloc_, first_ + offset, last_, alloc_result.data + offset + src_size); + { guard1.release(); guard2.release(); guard3.release(); } + detail::destroy_range(alloc_, first_, last_); + deallocate(); + set_storage(alloc_result.data, old_size + size_type(src_size), alloc_result.size); + + return alloc_result.data + offset; } constexpr void deallocate() noexcept