[libcxx-commits] [libcxx] [libc++] Optimize vector::erase (PR #213916)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Aug 24 01:54:45 PDT 2026
https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/213916
>From 0f771ac04ffe01a327fa01999871c85d7316721e Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Mon, 3 Aug 2026 15:05:19 +0200
Subject: [PATCH] [libc++] Optimize vector::erase
---
.../__memory/uninitialized_algorithms.h | 38 ++++++++++++-
libcxx/include/__vector/layout.h | 26 +++++++--
libcxx/include/__vector/vector.h | 57 +++++++++++--------
3 files changed, 89 insertions(+), 32 deletions(-)
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index e1c6fe25040ea..10fbd3de5af41 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -176,7 +176,7 @@ uninitialized_default_construct(_ForwardIterator __first, _ForwardIterator __las
template <class _ValueType, class _ForwardIterator, class _Size>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 _ForwardIterator
__uninitialized_default_construct_n(_ForwardIterator __first, _Size __n) {
- auto __idx = __first;
+ auto __idx = __first;
auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); });
for (; __n > 0; ++__idx, (void)--__n)
::new (static_cast<void*>(std::addressof(*__idx))) _ValueType;
@@ -218,7 +218,7 @@ uninitialized_value_construct(_ForwardIterator __first, _ForwardIterator __last)
template <class _ValueType, class _ForwardIterator, class _Size>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 _ForwardIterator
__uninitialized_value_construct_n(_ForwardIterator __first, _Size __n) {
- auto __idx = __first;
+ auto __idx = __first;
auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); });
for (; __n > 0; ++__idx, (void)--__n)
::new (static_cast<void*>(std::addressof(*__idx))) _ValueType();
@@ -302,6 +302,40 @@ uninitialized_move_n(_InputIterator __ifirst, _Size __n, _ForwardIterator __ofir
#endif // _LIBCPP_STD_VER >= 17
+// __uninitialized_relocate relocates the objects in [__first, __last) into __result element-by-element from __first to
+// __last. Relocation means that the objects in [__first, __last) are placed into __result as-if by move-construct and
+// destroy, except that the move constructor and destructor may never be called if they are known to be equivalent to a
+// memcpy.
+//
+// Preconditions: At __result there is no object and [__first, __last) contains objects
+// Postconditions: If no exceptions were thrown, __result contains the objects from [__first, __last), otherwise it
+// doesn't contain any objects.
+// [__first, __last) doesn't contain any objects.
+template <class _ContiguousIterator>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__uninitialized_relocate(_ContiguousIterator __first, _ContiguousIterator __last, _ContiguousIterator __result) {
+ static_assert(__libcpp_is_contiguous_iterator<_ContiguousIterator>::value, "");
+ using _ValueType = typename iterator_traits<_ContiguousIterator>::value_type;
+
+ // TODO: Accept non-trivially relocatable types
+ static_assert(__is_trivially_relocatable_v<_ValueType>);
+
+ if (__libcpp_is_constant_evaluated()) {
+ auto __iter = __first;
+ while (__iter != __last) {
+ std::__construct_at(std::__to_address(__result), std::move(*__iter));
+ std::__destroy_at(std::__to_address(__iter));
+ ++__iter;
+ ++__result;
+ }
+ } else {
+ // Casting to void* to suppress clang complaining that this is technically UB.
+ __builtin_memmove(static_cast<void*>(std::__to_address(__result)),
+ std::__to_address(__first),
+ sizeof(_ValueType) * (__last - __first));
+ }
+}
+
template <class _Alloc, class _Iter>
class _AllocatorDestroyRangeReverse {
public:
diff --git a/libcxx/include/__vector/layout.h b/libcxx/include/__vector/layout.h
index af03556dc2636..2d1a5f1be54d2 100644
--- a/libcxx/include/__vector/layout.h
+++ b/libcxx/include/__vector/layout.h
@@ -114,12 +114,13 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <class _Tp, class _Allocator>
class __vector_layout {
public:
- using value_type _LIBCPP_NODEBUG = _Tp;
- using allocator_type _LIBCPP_NODEBUG = _Allocator;
- using __alloc_traits _LIBCPP_NODEBUG = allocator_traits<allocator_type>;
- using size_type _LIBCPP_NODEBUG = typename __alloc_traits::size_type;
- using pointer _LIBCPP_NODEBUG = typename __alloc_traits::pointer;
- using const_pointer _LIBCPP_NODEBUG = typename __alloc_traits::const_pointer;
+ using value_type _LIBCPP_NODEBUG = _Tp;
+ using allocator_type _LIBCPP_NODEBUG = _Allocator;
+ using __alloc_traits _LIBCPP_NODEBUG = allocator_traits<allocator_type>;
+ using size_type _LIBCPP_NODEBUG = typename __alloc_traits::size_type;
+ using difference_type _LIBCPP_NODEBUG = typename __alloc_traits::difference_type;
+ using pointer _LIBCPP_NODEBUG = typename __alloc_traits::pointer;
+ using const_pointer _LIBCPP_NODEBUG = typename __alloc_traits::const_pointer;
#ifdef _LIBCPP_ABI_VECTOR_LAYOUT_SIZE_BASED
using _SplitBuffer _LIBCPP_NODEBUG = __split_buffer<_Tp, _Allocator, __split_buffer_size_layout>;
using __bound_type _LIBCPP_NODEBUG = size_type;
@@ -206,6 +207,8 @@ class __vector_layout {
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_pointer __capacity_ptr() const _NOEXCEPT;
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const _NOEXCEPT;
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __offset_size(difference_type __offset) _NOEXCEPT;
+
private:
pointer __begin_ = nullptr;
@@ -349,6 +352,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __vector_layout<_Tp, _Alloc>::__invariants()
return __size_ == 0 && __capacity_ == 0;
return __size_ <= __capacity_;
}
+
+template <class _Tp, class _Alloc>
+_LIBCPP_CONSTEXPR_SINCE_CXX20 void __vector_layout<_Tp, _Alloc>::__offset_size(difference_type __offset) _NOEXCEPT {
+ __size_ += __offset;
+}
#else // !defined(_LIBCPP_ABI_VECTOR_LAYOUT_SIZE_BASED)
template <class _Tp, class _Alloc>
_LIBCPP_CONSTEXPR_SINCE_CXX20 __vector_layout<_Tp, _Alloc>::__vector_layout(__vector_layout&& __other)
@@ -470,6 +478,12 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __vector_layout<_Tp, _Alloc>::__invariants()
return false;
return __end_ <= __capacity_;
}
+
+template <class _Tp, class _Alloc>
+_LIBCPP_CONSTEXPR_SINCE_CXX20 void __vector_layout<_Tp, _Alloc>::__offset_size(difference_type __offset) _NOEXCEPT {
+ __end_ += __offset;
+}
+
#endif // _LIBCPP_ABI_VECTOR_LAYOUT_SIZE_BASED
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 5d2ef8fc2a583..4f8a554ce7b07 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -552,8 +552,39 @@ class _LIBCPP_WARN_UNUSED vector {
}
#endif
- _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position);
- _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last);
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position) {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ __position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
+ return erase(__position, __position + 1);
+ }
+
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last) {
+ _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
+ pointer __pos = this->__layout_.__begin_ptr() + (__first - begin());
+ auto __hole_size = __last - __first;
+ if (__first != __last) {
+#ifndef _LIBCPP_CXX03_LANG
+ // If the value_type is trivially relocatable, we destroy the range being erased and we relocate the tail
+ // of the vector into the created gap. Otherwise, we use the standard technique with move-assignments.
+ //
+ // We really only need is_nothrow_reocatable, but vector::erase is currently overspecified to use assignment.
+ // We use is_trivially_relocatable unil that is relaxed.
+ if constexpr (__is_trivially_relocatable_v<value_type> &&
+ __allocator_has_trivial_move_construct_v<allocator_type, value_type> &&
+ __allocator_has_trivial_destroy_v<allocator_type, value_type>) {
+ auto __raw_pos = std::__to_address(__pos);
+ auto __old_size = size();
+ std::__destroy(__raw_pos, __raw_pos + __hole_size);
+ std::__uninitialized_relocate(__raw_pos + __hole_size, std::__to_address(__layout_.__end_ptr()), __raw_pos);
+ __layout_.__set_bound_using_pointer(__layout_.__end_ptr() - __hole_size);
+ __annotate_shrink(__old_size);
+ return __make_iter(__pos);
+ }
+#endif
+ this->__destruct_at_end(std::move(__pos + __hole_size, __layout_.__end_ptr(), __pos));
+ }
+ return __make_iter(__pos);
+ }
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT {
size_type __old_size = size();
@@ -1091,28 +1122,6 @@ vector<_Tp, _Alloc>::emplace_back(_Args&&... __args) {
#endif
}
-template <class _Tp, class _Allocator>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
-vector<_Tp, _Allocator>::erase(const_iterator __position) {
- _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
- __position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
- difference_type __ps = __position - cbegin();
- pointer __p = this->__layout_.__begin_ptr() + __ps;
- this->__destruct_at_end(std::move(__p + 1, __layout_.__end_ptr(), __p));
- return __make_iter(__p);
-}
-
-template <class _Tp, class _Allocator>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
-vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) {
- _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
- pointer __p = this->__layout_.__begin_ptr() + (__first - begin());
- if (__first != __last) {
- this->__destruct_at_end(std::move(__p + (__last - __first), __layout_.__end_ptr(), __p));
- }
- return __make_iter(__p);
-}
-
template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 void
vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointer __to) {
More information about the libcxx-commits
mailing list