Skip to content

Commit 79e6086

Browse files
committed
Take advantage of trivial relocation in std::vector::erase
1 parent 3c53efd commit 79e6086

File tree

2 files changed

+40
-24
lines changed

2 files changed

+40
-24
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/__vector/vector.h

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@
3434
#include <__memory/allocator.h>
3535
#include <__memory/allocator_traits.h>
3636
#include <__memory/compressed_pair.h>
37+
#include <__memory/is_trivially_allocator_relocatable.h>
3738
#include <__memory/noexcept_move_assign_container.h>
3839
#include <__memory/pointer_traits.h>
3940
#include <__memory/swap_allocator.h>
4041
#include <__memory/temp_value.h>
4142
#include <__memory/uninitialized_algorithms.h>
43+
#include <__memory/uninitialized_relocate.h>
4244
#include <__ranges/access.h>
4345
#include <__ranges/concepts.h>
4446
#include <__ranges/container_compatible_range.h>
@@ -518,8 +520,41 @@ class _LIBCPP_TEMPLATE_VIS vector {
518520
}
519521
#endif
520522

521-
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position);
522-
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last);
523+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position) {
524+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
525+
__position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
526+
return erase(__position, __position + 1);
527+
}
528+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __cfirst, const_iterator __clast) {
529+
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__cfirst <= __clast, "vector::erase(first, last) called with invalid range");
530+
531+
iterator __first = begin() + std::distance(cbegin(), __cfirst);
532+
iterator __last = begin() + std::distance(cbegin(), __clast);
533+
if (__first == __last)
534+
return __last;
535+
536+
auto const __n = std::distance(__first, __last);
537+
538+
// If the value_type is nothrow relocatable, we destroy the range being erased and we relocate the tail
539+
// of the vector into the created gap. This is especially efficient if the elements are trivially relocatable.
540+
// Otherwise, we use the standard technique with move-assignments.
541+
//
542+
// Note that we also require __is_replaceable here for backwards compatibility, because we used
543+
// to perform move-assignments unconditionally. If we didn't enforce that, we would no longer call
544+
// the assignment operator of types that have a funky operator= and expect it to be called in
545+
// vector::erase.
546+
if constexpr (__is_replaceable<value_type>::value && __is_nothrow_allocator_relocatable<_Allocator, value_type>::value) {
547+
std::__allocator_destroy(this->__alloc_, __first, __last);
548+
std::__uninitialized_allocator_relocate(this->__alloc_, __last, end(), __first);
549+
} else {
550+
auto __new_end = std::move(__last, end(), __first);
551+
std::__allocator_destroy(this->__alloc_, __new_end, end());
552+
}
553+
554+
this->__end_ -= __n;
555+
__annotate_shrink(size() + __n);
556+
return __first;
557+
}
523558

524559
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT {
525560
size_type __old_size = size();
@@ -1113,28 +1148,6 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 inline
11131148
#endif
11141149
}
11151150

1116-
template <class _Tp, class _Allocator>
1117-
_LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
1118-
vector<_Tp, _Allocator>::erase(const_iterator __position) {
1119-
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
1120-
__position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
1121-
difference_type __ps = __position - cbegin();
1122-
pointer __p = this->__begin_ + __ps;
1123-
this->__destruct_at_end(std::move(__p + 1, this->__end_, __p));
1124-
return __make_iter(__p);
1125-
}
1126-
1127-
template <class _Tp, class _Allocator>
1128-
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
1129-
vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) {
1130-
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
1131-
pointer __p = this->__begin_ + (__first - begin());
1132-
if (__first != __last) {
1133-
this->__destruct_at_end(std::move(__p + (__last - __first), this->__end_, __p));
1134-
}
1135-
return __make_iter(__p);
1136-
}
1137-
11381151
template <class _Tp, class _Allocator>
11391152
_LIBCPP_CONSTEXPR_SINCE_CXX20 void
11401153
vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointer __to) {

0 commit comments

Comments
 (0)