Skip to content

Commit

Permalink
insert/emplace implementation improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
KRM7 committed Mar 15, 2024
1 parent 87a1bcd commit 4af1c66
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 68 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down
29 changes: 22 additions & 7 deletions benchmark/small_vector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ inline constexpr size_t LARGE_SIZE = 100;
template<typename V>
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();
Expand All @@ -29,11 +30,14 @@ BENCHMARK(benchmark_construct_from_size<small_vector<int>>)->ArgName("size")->Ar
template<typename V>
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();
}
Expand All @@ -47,10 +51,11 @@ BENCHMARK(benchmark_construct_from_size_value<small_vector<int>>)->ArgName("size
template<typename V>
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();
Expand All @@ -72,6 +77,8 @@ void benchmark_assign_reserved(benchmark::State& state)

for (auto _ : state)
{
benchmark::DoNotOptimize(src);

dst.clear();
benchmark::DoNotOptimize(dst);

Expand Down Expand Up @@ -113,12 +120,14 @@ BENCHMARK(benchmark_swap<small_vector<int>>)->ArgName("size")->Arg(SMALL_SIZE)->
template<typename V>
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);

Expand Down Expand Up @@ -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();
Expand All @@ -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<int>::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();
}
}
Expand Down
186 changes: 126 additions & 60 deletions src/small_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
};

Expand Down Expand Up @@ -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<A>::max_size(alloc_); }

Expand Down Expand Up @@ -793,9 +790,7 @@ class small_vector
constexpr reference emplace_back(Args&&... args)
{
if (last_ != last_alloc_) return emplace_back_unchecked(std::forward<Args>(args)...);

reallocate_append(next_capacity(), std::forward<Args>(args)...);
return back();
return *reallocate_append(next_capacity(), std::forward<Args>(args)...);
}

template<typename... Args>
Expand All @@ -818,64 +813,73 @@ class small_vector
template<typename... Args>
constexpr iterator emplace(const_iterator pos, Args&&... args)
{
if (pos == cend()) return std::addressof(emplace_back(std::forward<Args>(args)...));
if (size() != capacity())
{
if (pos == cend()) return std::addressof(emplace_back_unchecked(std::forward<Args>(args)...));

detail::allocator_managed<T, A> new_elem(alloc_, std::forward<Args>(args)...);
detail::allocator_managed<T, A> new_elem(alloc_, std::forward<Args>(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>(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<difference_type>(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<std::forward_iterator Iter>
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<std::input_iterator Iter>
Expand Down Expand Up @@ -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<A> alloc_result = detail::allocate<T>(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<A> alloc_result = detail::allocate<T>(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)
Expand All @@ -955,7 +955,7 @@ class small_vector
}

template<typename... Args>
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();

Expand All @@ -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<typename... Args>
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<A> alloc_result = detail::allocate<T>(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>(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<A> alloc_result = detail::allocate<T>(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<std::forward_iterator Iter>
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<A> alloc_result = detail::allocate<T>(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
Expand Down

0 comments on commit 4af1c66

Please sign in to comment.