[libc++] Optimize vector::emplace_back - #218658
Conversation
|
/libcxx-bot benchmark libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
|
260eca1 to
cedd02e
Compare
|
/libcxx-bot benchmark libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
Benchmark results for linux-x86_64-20260812:Benchmark results for macos-26.5-arm64-20260812:Benchmark results for macos-26.5-arm64-hardenedfast-20260821: |
|
@llvm/pr-subscribers-libcxx Author: Nikolas Klauser (philnik777) ChangesThis avoids using a lot of stack memory for Full diff: https://github.com/llvm/llvm-project/pull/218658.diff 1 Files Affected:
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index ebcfcc17d4b6d..dad2f5f48596a 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -754,9 +754,6 @@ class _LIBCPP_WARN_UNUSED vector {
__annotate_shrink(__old_size);
}
- template <class... _Args>
- _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI inline pointer __emplace_back_slow_path(_Args&&... __args);
-
// The following functions are no-ops outside of AddressSanitizer mode.
// We call annotations for every allocator, unless explicitly disabled.
//
@@ -1071,34 +1068,25 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::shrink_to_fit() _NOE
}
}
-template <class _Tp, class _Allocator>
-template <class... _Args>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::pointer
-vector<_Tp, _Allocator>::__emplace_back_slow_path(_Args&&... __args) {
- _SplitBuffer __v(__recommend(size() + 1), size(), this->__layout_.__alloc());
- // __v.emplace_back(std::forward<_Args>(__args)...);
- pointer __end = __v.end();
- __alloc_traits::construct(this->__layout_.__alloc(), std::__to_address(__end), std::forward<_Args>(__args)...);
- __v.__set_sentinel(++__end);
- __layout_.__relocate(__v);
- return __end;
-}
-
// This makes the compiler inline `__else()` if `__cond` is known to be false. Currently LLVM doesn't do that without
// the `__builtin_constant_p`, since it considers `__else` unlikely even through it's known to be run.
// See https://llvm.org/PR154292
-template <class _If, class _Else>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __if_likely_else(bool __cond, _If __if, _Else __else) {
+template <class _If, class _Else, class... _Args>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 decltype(std::declval<_If>()(std::declval<_Args&&>()...))
+__if_likely_else(bool __cond, _If __if, _Else __else, _Args&&... __args) {
+ static_assert(
+ is_same<decltype(__if(std::forward<_Args>(__args)...)), decltype(__else(std::forward<_Args>(__args)...))>::value,
+ "Return type of if and else branch have to be the same type");
if (__builtin_constant_p(__cond)) {
if (__cond)
- __if();
+ return __if(std::forward<_Args>(__args)...);
else
- __else();
+ return __else(std::forward<_Args>(__args)...);
} else {
if (__cond) [[__likely__]]
- __if();
+ return __if(std::forward<_Args>(__args)...);
else
- __else();
+ return __else(std::forward<_Args>(__args)...);
}
}
@@ -1106,16 +1094,23 @@ template <class _Tp, class _Alloc>
template <class... _Args>
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Alloc>::__emplace_back_result_t
vector<_Tp, _Alloc>::emplace_back(_Args&&... __args) {
- pointer __end = __layout_.__end_ptr();
std::__if_likely_else(
size() != capacity(),
- [&] {
- __emplace_back_assume_capacity(std::forward<_Args>(__args)...);
- ++__end;
+ [](vector& __self, _Args&&... __largs) static {
+ __self.__emplace_back_assume_capacity(std::forward<_Args>(__largs)...);
+ },
+ [](vector& __self, _Args&&... __largs) static {
+ _SplitBuffer __v(__self.__recommend(__self.size() + 1), __self.size(), __self.__layout_.__alloc());
+ // __v.emplace_back(std::forward<_Args>(__args)...);
+ pointer __end = __v.end();
+ __alloc_traits::construct(
+ __self.__layout_.__alloc(), std::__to_address(__end), std::forward<_Args>(__largs)...);
+ __v.__set_sentinel(++__end);
+ __self.__layout_.__relocate(__v);
},
- [&] { __end = __emplace_back_slow_path(std::forward<_Args>(__args)...); });
+ *this,
+ std::forward<_Args>(__args)...);
- __layout_.__set_bound_using_pointer(__end);
#if _LIBCPP_STD_VER >= 17
return back();
#endif
|
|
@liza371 This might also fix your issue with |
Oh wow this does help a lot this is awesome. Just for reference here's the binary size diff with this specific optimization: https://chrome-supersize.firebaseapp.com/viewer.html?load_url=https%3A%2F%2Fstorage.googleapis.com%2Fchrome-supersize%2Foneoffs%2Fb16984ce99c702355a5b2b4c52574e82cec41fb9_4b112225b8291de813e1d486a4265bea054f4115.sizediff&group_by=template You also rightly pointed out I didn't include an analysis with the ABI turned off. Here's an analysis with the sized based vector ABI turned off for reference I haven't run this analysis with the unstable ABI fully turned off as chromium depends on other features pretty heavily so it would be non-trivial to do so. |
|
@liza371 That tool looks amazing. I'm trying to understand the output though: does it mean that applying this patch increases the overall size of Chromium by 140.35 KiB (for unstable ABI) and 62.68 KiB (for stable ABI) no matter what? Isn't that a net regression? I think this is just how I'm reading the tool's output. |
ldionne
left a comment
There was a problem hiding this comment.
I like the code changes, but the benchmarks are not very conclusive. Do we think this is simply because our benchmarks are not sensitive enough to capture this improvement?
|
@ldionne not quite, I should have linked my other PR sorry about that. I opened #216822 to address the binary size bloat we were seeing in Chromium, and I have links there to show the initial bloat. Applying this patch still leads to an increase in Chromium's binary size, but it's a much smaller increase than we saw when we pulled in size-based vector. I initially was also trying to land some changes to emplace_back that @cjdb wrote but @philnik777 noted that they would remove a lot of other performance optimizations, so I shelved that. #216822 still has some small patches, but I got sidetracked and now I'm admittedly still not entirely sure why the inlining does help with the bloat, so I wanted to do some more investigating to get a more definitive answer than "idk the tool says so" before coming back to that PR. |
Yes, I think so. |
ldionne
left a comment
There was a problem hiding this comment.
@liza371 Just to clarify, even if we land this patch, you're still blocked from rolling libc++ because of the size increase, it's just not as bad, right? In other words, it seems that we still need to figure out what made __relocate (from this) go larger, no matter what.
@liza371 What optimization level does Chrome build with?
Either way, I think this change is a net positive so let's go with it, but let's figure out what went wrong with __relocate as well.
|
@ldionne yes that's correct. We're building with I did just attempt a build with These are both without the |
This avoids using a lot of stack memory for `emplace_back` by making the outlined lambda `static`. This also reduces the number of instructions to execute a simple `emplace_back`.
This avoids using a lot of stack memory for `emplace_back` by making the outlined lambda `static`. This also reduces the number of instructions to execute a simple `emplace_back`.
This avoids using a lot of stack memory for
emplace_backby making the outlined lambdastatic. This also reduces the number of instructions to execute a simpleemplace_back.