[libcxx-commits] [libcxx] [libc++] Optimize vector::emplace (PR #218665)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Aug 25 05:09:54 PDT 2026
https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/218665
None
>From 71aacdfdd6f3c96f254ec58404e89f78c68dd59a Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Tue, 25 Aug 2026 14:09:33 +0200
Subject: [PATCH] [libc++] Optimize vector::emplace
---
.../__memory/uninitialized_algorithms.h | 38 ++++++++++++++++
libcxx/include/__type_traits/is_relocatable.h | 7 +++
libcxx/include/__vector/vector.h | 43 ++++++++++++++++---
.../destroy_elements.pass.cpp | 2 +
4 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index 82a7a20be4007..5a69632b81cbf 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -337,6 +337,44 @@ __uninitialized_relocate(_ContiguousIterator __first, _ContiguousIterator __last
}
}
+// __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_backward(_ContiguousIterator __first, _ContiguousIterator __last, _ContiguousIterator __result) {
+ static_assert(__libcpp_is_contiguous_iterator<_ContiguousIterator>::value, "");
+ using _ValueType = typename iterator_traits<_ContiguousIterator>::value_type;
+
+ if (__libcpp_is_constant_evaluated() || !__is_trivially_relocatable_v<_ValueType>) {
+ size_t __count = __last - __first;
+ __result += __count;
+ size_t __i = 0;
+
+ auto __guard = std::__make_exception_guard([&] {
+ std::__destroy(__result, __result + __i);
+ std::__destroy(__first, __last);
+ });
+
+ for (; __i != __count; ++__i) {
+ std::__construct_at(std::__to_address(--__result), std::move(*--__last));
+ std::__destroy_at(std::__to_address(__last));
+ }
+ __guard.__complete();
+ } 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/__type_traits/is_relocatable.h b/libcxx/include/__type_traits/is_relocatable.h
index cfbe1966a67b2..e9b6ab53e9047 100644
--- a/libcxx/include/__type_traits/is_relocatable.h
+++ b/libcxx/include/__type_traits/is_relocatable.h
@@ -11,6 +11,8 @@
#include <__config>
#include <__type_traits/enable_if.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_nothrow_destructible.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_trivially_copyable.h>
@@ -43,6 +45,11 @@ inline const bool
true;
#endif
+template <class _Tp>
+inline const bool __is_nothrow_relocatable_v =
+ __is_trivially_relocatable_v<_Tp> ||
+ (is_nothrow_move_constructible<_Tp>::value && is_nothrow_destructible<_Tp>::value);
+
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___TYPE_TRAITS_IS_RELOCATABLE_H
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index e23bd8999e48a..7cd497fa946b0 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -36,6 +36,7 @@
#include <__memory/allocator.h>
#include <__memory/allocator_traits.h>
#include <__memory/compressed_pair.h>
+#include <__memory/construct_at.h>
#include <__memory/noexcept_move_assign_container.h>
#include <__memory/pointer_traits.h>
#include <__memory/swap_allocator.h>
@@ -1142,13 +1143,41 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) {
pointer __p = this->__layout_.__begin_ptr() + (__position - begin());
if (size() != capacity()) {
- pointer __end = __layout_.__end_ptr();
- if (__p == __end) {
- __emplace_back_assume_capacity(std::forward<_Args>(__args)...);
- } else {
- __temp_value<value_type, _Allocator> __tmp(this->__layout_.__alloc(), std::forward<_Args>(__args)...);
- __move_range(__p, __end, __p + 1);
- *__p = std::move(__tmp.get());
+#ifndef _LIBCPP_CXX03_LANG
+ if constexpr (__allocator_has_trivial_move_construct_v<allocator_type, value_type> &&
+ __allocator_has_trivial_destroy_v<allocator_type, value_type> &&
+ (__is_nothrow_relocatable_v<value_type> || !is_nothrow_move_assignable<value_type>::value)) {
+ union _Union {
+ constexpr _Union() {}
+ constexpr ~_Union() {}
+
+ value_type __tmp_;
+ } __tmp;
+
+ std::__construct_at(std::addressof(__tmp.__tmp_), std::forward<_Args>(__args)...);
+ auto __raw_pos = std::__to_address(__p);
+
+ auto __tail_guard = std::__make_exception_guard([&] { __layout_.__set_bound_using_pointer(__p); });
+ std::__uninitialized_relocate_backward(__raw_pos, std::__to_address(__layout_.__end_ptr()), __raw_pos + 1);
+ __tail_guard.__complete();
+
+ auto __elem_guard = std::__make_exception_guard([&] {
+ std::__destroy(__raw_pos + 1, std::__to_address(__layout_.__end_ptr() + 1));
+ });
+ std::__uninitialized_relocate_backward(std::addressof(__tmp.__tmp_), std::addressof(__tmp.__tmp_) + 1, __raw_pos);
+ __elem_guard.__complete();
+ __layout_.__set_bound_using_pointer(__layout_.__end_ptr() + 1);
+ } else
+#endif
+ {
+ pointer __end = __layout_.__end_ptr();
+ if (__p == __end) {
+ __emplace_back_assume_capacity(std::forward<_Args>(__args)...);
+ } else {
+ __temp_value<value_type, _Allocator> __tmp(this->__layout_.__alloc(), std::forward<_Args>(__args)...);
+ __move_range(__p, __end, __p + 1);
+ *__p = std::move(__tmp.get());
+ }
}
} else {
_SplitBuffer __v(__recommend(size() + 1), __p - this->__layout_.__begin_ptr(), this->__layout_.__alloc());
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/destroy_elements.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/destroy_elements.pass.cpp
index 179c5ac955f05..c2f828dd02b33 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/destroy_elements.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/destroy_elements.pass.cpp
@@ -10,6 +10,8 @@
// UNSUPPORTED: c++03
+// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-steps): -fconstexpr-steps=2000000
+
#include <algorithm>
#include <array>
#include <cassert>
More information about the libcxx-commits
mailing list