[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