Skip to content

Commit b3b3d05

Browse files
committed
[WIP] Implement std::vector operations in terms of relocation
1 parent 4986d1c commit b3b3d05

File tree

6 files changed

+123
-163
lines changed

6 files changed

+123
-163
lines changed

libcxx/docs/ReleaseNotes/20.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ Improvements and New Features
5252
- The ``lexicographical_compare`` and ``ranges::lexicographical_compare`` algorithms have been optimized for trivially
5353
equality comparable types, resulting in a performance improvement of up to 40x.
5454

55+
- The ``std::vector::erase`` function has been optimized for types that can be relocated trivially (such as ``std::string``),
56+
yielding speed ups witnessed to be around 2x for these types (but subject to the use case).
57+
5558
- The ``_LIBCPP_ENABLE_CXX20_REMOVED_TEMPORARY_BUFFER`` macro has been added to make ``std::get_temporary_buffer`` and
5659
``std::return_temporary_buffer`` available.
5760

libcxx/include/__memory/temp_value.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@
2121

2222
_LIBCPP_BEGIN_NAMESPACE_STD
2323

24+
template <class _Tp>
25+
struct __temporary_emplace_value {
26+
union {
27+
_Tp __value_;
28+
};
29+
30+
template <class _Allocator, class... _Args>
31+
_LIBCPP_HIDE_FROM_ABI
32+
_LIBCPP_CONSTEXPR_SINCE_CXX20 explicit __temporary_emplace_value(_Allocator& __alloc, _Args&&... __args) {
33+
allocator_traits<_Allocator>::construct(__alloc, std::addressof(__value_), std::forward<_Args>(__args)...);
34+
}
35+
36+
// Don't destroy anything, since we assume that the value gets relocated by whoever uses this type
37+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 ~__temporary_emplace_value() {}
38+
39+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __address() { return std::addressof(__value_); }
40+
};
41+
2442
template <class _Tp, class _Alloc>
2543
struct __temp_value {
2644
typedef allocator_traits<_Alloc> _Traits;

libcxx/include/__vector/vector.h

Lines changed: 102 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@
3434
#include <__memory/allocator.h>
3535
#include <__memory/allocator_traits.h>
3636
#include <__memory/compressed_pair.h>
37+
#include <__memory/destroy.h>
38+
#include <__memory/is_trivially_allocator_relocatable.h>
3739
#include <__memory/noexcept_move_assign_container.h>
3840
#include <__memory/pointer_traits.h>
3941
#include <__memory/swap_allocator.h>
4042
#include <__memory/temp_value.h>
4143
#include <__memory/uninitialized_algorithms.h>
44+
#include <__memory/uninitialized_relocate.h>
4245
#include <__ranges/access.h>
4346
#include <__ranges/concepts.h>
4447
#include <__ranges/container_compatible_range.h>
@@ -472,11 +475,68 @@ class _LIBCPP_TEMPLATE_VIS vector {
472475
this->__destruct_at_end(this->__end_ - 1);
473476
}
474477

475-
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, const_reference __x);
478+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, const_reference __x) {
479+
return emplace(std::move(__position), __x);
480+
}
476481

477-
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, value_type&& __x);
482+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, value_type&& __x) {
483+
return emplace(std::move(__position), std::move(__x));
484+
}
478485
template <class... _Args>
479-
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator emplace(const_iterator __position, _Args&&... __args);
486+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator emplace(const_iterator __cposition, _Args&&... __args) {
487+
iterator __position = begin() + (__cposition - cbegin());
488+
if (__end_ < __cap_) {
489+
if (__position == end()) {
490+
allocator_traits<_Allocator>::construct(
491+
__alloc_, std::__to_address(__position), std::forward<_Args>(__args)...);
492+
++__end_;
493+
} else {
494+
// Construct a temporary value on the stack, so that in case this throws we haven't modified
495+
// the vector yet. This also takes care of the corner case where we'd be trying to insert
496+
// from an element located in the vector itself, in which case we'd otherwise have to be
497+
// careful about reference invalidation if we didn't make a temporary value.
498+
__temporary_emplace_value<value_type> __tmp(__alloc_, std::forward<_Args>(__args)...);
499+
auto __guard = std::__make_exception_guard([&] {
500+
std::allocator_traits<_Allocator>::destroy(__alloc_, __tmp.__address());
501+
});
502+
503+
// Open up a gap inside the vector by relocating everything to the right and insert the new
504+
// element into the right spot.
505+
//
506+
// If this fails, the relocation operation guarantees that the whole tail of the vector has
507+
// been destroyed. So we set the "new end" of the vector accordingly, which means we basically
508+
// erased the whole tail of the vector. This provides the basic exception guarantee.
509+
try {
510+
std::__uninitialized_allocator_relocate_backward(__alloc_, __position, end(), __position + 1);
511+
} catch (...) {
512+
__end_ = __to_pointer(__position);
513+
throw;
514+
}
515+
516+
// Relocate the temporary value into its final location. If that throws, we know from __allocator_relocate_at
517+
// that the temporary will have been destroyed, but we must still clear the tail of the vector, since otherwise
518+
// we'd leave a gap in the middle of the vector.
519+
__guard.__complete(); // we know the temporary value gets destroyed by the relocation no matter what happens
520+
try {
521+
std::__allocator_relocate_at(__alloc_, __tmp.__address(), std::__to_address(__position));
522+
} catch (...) {
523+
std::__allocator_destroy(__alloc_, __position + 1, end() + 1);
524+
__end_ = __to_pointer(__position);
525+
throw;
526+
}
527+
528+
++__end_;
529+
}
530+
__annotate_increase(1);
531+
return __position;
532+
} else {
533+
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __position - begin(), __alloc_);
534+
__v.emplace_back(std::forward<_Args>(__args)...);
535+
pointer __p = __to_pointer(__position);
536+
__p = __swap_out_circular_buffer(__v, __p);
537+
return __make_iter(__p);
538+
}
539+
}
480540

481541
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator
482542
insert(const_iterator __position, size_type __n, const_reference __x);
@@ -520,8 +580,40 @@ class _LIBCPP_TEMPLATE_VIS vector {
520580
}
521581
#endif
522582

523-
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position);
524-
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last);
583+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position) {
584+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
585+
__position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
586+
return erase(__position, __position + 1);
587+
}
588+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __cfirst, const_iterator __clast) {
589+
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__cfirst <= __clast, "vector::erase(first, last) called with invalid range");
590+
591+
iterator __first = begin() + std::distance(cbegin(), __cfirst);
592+
iterator __last = begin() + std::distance(cbegin(), __clast);
593+
if (__first == __last)
594+
return __last;
595+
596+
auto const __n = std::distance(__first, __last);
597+
598+
// If the value_type is nothrow relocatable, we destroy the range being erased and we relocate the tail
599+
// of the vector into the created gap. This is especially efficient if the elements are trivially relocatable.
600+
// Otherwise, we use the standard technique with move-assignments.
601+
//
602+
// Note that unlike vector::insert, we can't use relocation when it is potentially throwing, because
603+
// vector::erase is required not to throw an exception unless T's assignment operator throws. So we
604+
// can bypass the assignment with a relocation, but only when that definitely doesn't throw.
605+
if constexpr (__is_nothrow_allocator_relocatable<_Allocator, value_type>::value) {
606+
std::__allocator_destroy(__alloc_, __first, __last);
607+
std::__uninitialized_allocator_relocate(__alloc_, __last, end(), __first);
608+
} else {
609+
auto __new_end = std::move(__last, end(), __first);
610+
std::__allocator_destroy(__alloc_, __new_end, end());
611+
}
612+
613+
__end_ -= __n;
614+
__annotate_shrink(size() + __n);
615+
return __first;
616+
}
525617

526618
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT {
527619
size_type __old_size = size();
@@ -662,6 +754,11 @@ class _LIBCPP_TEMPLATE_VIS vector {
662754
__annotate_shrink(__old_size);
663755
}
664756

757+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer __to_pointer(iterator __it) const {
758+
auto __index = __it - begin();
759+
return this->__begin_ + __index;
760+
}
761+
665762
template <class... _Args>
666763
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI inline pointer __emplace_back_slow_path(_Args&&... __args);
667764

@@ -1115,28 +1212,6 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 inline
11151212
#endif
11161213
}
11171214

1118-
template <class _Tp, class _Allocator>
1119-
_LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
1120-
vector<_Tp, _Allocator>::erase(const_iterator __position) {
1121-
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
1122-
__position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
1123-
difference_type __ps = __position - cbegin();
1124-
pointer __p = this->__begin_ + __ps;
1125-
this->__destruct_at_end(std::move(__p + 1, this->__end_, __p));
1126-
return __make_iter(__p);
1127-
}
1128-
1129-
template <class _Tp, class _Allocator>
1130-
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
1131-
vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) {
1132-
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
1133-
pointer __p = this->__begin_ + (__first - begin());
1134-
if (__first != __last) {
1135-
this->__destruct_at_end(std::move(__p + (__last - __first), this->__end_, __p));
1136-
}
1137-
return __make_iter(__p);
1138-
}
1139-
11401215
template <class _Tp, class _Allocator>
11411216
_LIBCPP_CONSTEXPR_SINCE_CXX20 void
11421217
vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointer __to) {
@@ -1152,68 +1227,6 @@ vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointe
11521227
std::move_backward(__from_s, __from_s + __n, __old_last);
11531228
}
11541229

1155-
template <class _Tp, class _Allocator>
1156-
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
1157-
vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) {
1158-
pointer __p = this->__begin_ + (__position - begin());
1159-
if (this->__end_ < this->__cap_) {
1160-
if (__p == this->__end_) {
1161-
__construct_one_at_end(__x);
1162-
} else {
1163-
__move_range(__p, this->__end_, __p + 1);
1164-
const_pointer __xr = pointer_traits<const_pointer>::pointer_to(__x);
1165-
if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x)))
1166-
++__xr;
1167-
*__p = *__xr;
1168-
}
1169-
} else {
1170-
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, this->__alloc_);
1171-
__v.emplace_back(__x);
1172-
__p = __swap_out_circular_buffer(__v, __p);
1173-
}
1174-
return __make_iter(__p);
1175-
}
1176-
1177-
template <class _Tp, class _Allocator>
1178-
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
1179-
vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) {
1180-
pointer __p = this->__begin_ + (__position - begin());
1181-
if (this->__end_ < this->__cap_) {
1182-
if (__p == this->__end_) {
1183-
__construct_one_at_end(std::move(__x));
1184-
} else {
1185-
__move_range(__p, this->__end_, __p + 1);
1186-
*__p = std::move(__x);
1187-
}
1188-
} else {
1189-
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, this->__alloc_);
1190-
__v.emplace_back(std::move(__x));
1191-
__p = __swap_out_circular_buffer(__v, __p);
1192-
}
1193-
return __make_iter(__p);
1194-
}
1195-
1196-
template <class _Tp, class _Allocator>
1197-
template <class... _Args>
1198-
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
1199-
vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) {
1200-
pointer __p = this->__begin_ + (__position - begin());
1201-
if (this->__end_ < this->__cap_) {
1202-
if (__p == this->__end_) {
1203-
__construct_one_at_end(std::forward<_Args>(__args)...);
1204-
} else {
1205-
__temp_value<value_type, _Allocator> __tmp(this->__alloc_, std::forward<_Args>(__args)...);
1206-
__move_range(__p, this->__end_, __p + 1);
1207-
*__p = std::move(__tmp.get());
1208-
}
1209-
} else {
1210-
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, this->__alloc_);
1211-
__v.emplace_back(std::forward<_Args>(__args)...);
1212-
__p = __swap_out_circular_buffer(__v, __p);
1213-
}
1214-
return __make_iter(__p);
1215-
}
1216-
12171230
template <class _Tp, class _Allocator>
12181231
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
12191232
vector<_Tp, _Allocator>::insert(const_iterator __position, size_type __n, const_reference __x) {

libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,6 @@ struct Throws {
4040
bool Throws::sThrows = false;
4141
#endif
4242

43-
struct Tracker {
44-
int copy_assignments = 0;
45-
int move_assignments = 0;
46-
};
47-
48-
struct TrackedAssignment {
49-
Tracker* tracker_;
50-
TEST_CONSTEXPR_CXX14 explicit TrackedAssignment(Tracker* tracker) : tracker_(tracker) {}
51-
52-
TrackedAssignment(TrackedAssignment const&) = default;
53-
TrackedAssignment(TrackedAssignment&&) = default;
54-
55-
TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment const&) {
56-
tracker_->copy_assignments++;
57-
return *this;
58-
}
59-
TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment&&) {
60-
tracker_->move_assignments++;
61-
return *this;
62-
}
63-
};
64-
6543
struct NonTriviallyRelocatable {
6644
int value_;
6745
TEST_CONSTEXPR NonTriviallyRelocatable() : value_(0) {}

libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -107,31 +107,5 @@ int main(int, char**) {
107107
}
108108
#endif
109109

110-
// Make sure we satisfy the complexity requirement in terms of the number of times the assignment
111-
// operator is called.
112-
//
113-
// There is currently ambiguity as to whether this is truly mandated by the Standard, so we only
114-
// test it for libc++.
115-
#ifdef _LIBCPP_VERSION
116-
{
117-
Tracker tracker;
118-
std::vector<TrackedAssignment> v;
119-
120-
// Set up the vector with 5 elements.
121-
for (int i = 0; i != 5; ++i) {
122-
v.emplace_back(&tracker);
123-
}
124-
assert(tracker.copy_assignments == 0);
125-
assert(tracker.move_assignments == 0);
126-
127-
// Erase element [1] from it. Elements [2] [3] [4] should be shifted, so we should
128-
// see 3 move assignments (and nothing else).
129-
v.erase(v.begin() + 1);
130-
assert(v.size() == 4);
131-
assert(tracker.copy_assignments == 0);
132-
assert(tracker.move_assignments == 3);
133-
}
134-
#endif
135-
136110
return 0;
137111
}

libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -196,31 +196,5 @@ int main(int, char**) {
196196
assert(it == v.begin() + 2);
197197
}
198198

199-
// Make sure we satisfy the complexity requirement in terms of the number of times the assignment
200-
// operator is called.
201-
//
202-
// There is currently ambiguity as to whether this is truly mandated by the Standard, so we only
203-
// test it for libc++.
204-
#ifdef _LIBCPP_VERSION
205-
{
206-
Tracker tracker;
207-
std::vector<TrackedAssignment> v;
208-
209-
// Set up the vector with 5 elements.
210-
for (int i = 0; i != 5; ++i) {
211-
v.emplace_back(&tracker);
212-
}
213-
assert(tracker.copy_assignments == 0);
214-
assert(tracker.move_assignments == 0);
215-
216-
// Erase elements [1] and [2] from it. Elements [3] [4] should be shifted, so we should
217-
// see 2 move assignments (and nothing else).
218-
v.erase(v.begin() + 1, v.begin() + 3);
219-
assert(v.size() == 3);
220-
assert(tracker.copy_assignments == 0);
221-
assert(tracker.move_assignments == 2);
222-
}
223-
#endif
224-
225199
return 0;
226200
}

0 commit comments

Comments
 (0)