-
Notifications
You must be signed in to change notification settings - Fork 15k
[libc++] Add move constructor & assignment to exception_ptr
#164281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[libc++] Add move constructor & assignment to exception_ptr
#164281
Conversation
You can test this locally with the following command:git-clang-format --diff origin/main HEAD --extensions cpp,h -- libcxx/include/__exception/exception_ptr.h libcxx/test/benchmarks/exception_ptr.bench.cpp --diff_from_common_commit
View the diff from clang-format here.diff --git a/libcxx/test/benchmarks/exception_ptr.bench.cpp b/libcxx/test/benchmarks/exception_ptr.bench.cpp
index 5eee9f70c..bd73f8b50 100644
--- a/libcxx/test/benchmarks/exception_ptr.bench.cpp
+++ b/libcxx/test/benchmarks/exception_ptr.bench.cpp
@@ -36,7 +36,6 @@ void bm_exception_ptr_copy_ctor_null(benchmark::State& state) {
}
BENCHMARK(bm_exception_ptr_copy_ctor_null);
-
void bm_exception_ptr_move_ctor_nonnull(benchmark::State& state) {
std::exception_ptr excptr = std::make_exception_ptr(42);
for (auto _ : state) {
@@ -80,7 +79,6 @@ void bm_exception_ptr_copy_assign_null(benchmark::State& state) {
}
BENCHMARK(bm_exception_ptr_copy_assign_null);
-
void bm_exception_ptr_move_assign_nonnull(benchmark::State& state) {
std::exception_ptr excptr = std::make_exception_ptr(42);
for (auto _ : state) {
@@ -107,7 +105,7 @@ void bm_exception_ptr_move_assign_null(benchmark::State& state) {
BENCHMARK(bm_exception_ptr_move_assign_null);
void bm_exception_ptr_swap_nonnull(benchmark::State& state) {
- std::exception_ptr excptr = std::make_exception_ptr(41);
+ std::exception_ptr excptr = std::make_exception_ptr(41);
std::exception_ptr excptr2 = std::make_exception_ptr(42);
for (auto _ : state) {
swap(excptr, excptr2);
@@ -118,7 +116,7 @@ void bm_exception_ptr_swap_nonnull(benchmark::State& state) {
BENCHMARK(bm_exception_ptr_swap_nonnull);
void bm_exception_ptr_swap_null(benchmark::State& state) {
- std::exception_ptr excptr = nullptr;
+ std::exception_ptr excptr = nullptr;
std::exception_ptr excptr2 = nullptr;
for (auto _ : state) {
benchmark::DoNotOptimize(excptr);
|
This commit adds benchmarks for `std::exception_ptr` to set a baseline in preparation for follow-up optimizations.
fef6eee to
b595542
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we instead add a swap and implement operator= as swap(exception_ptr())? That would avoid having to introduce any new symbols, at least in this patch.
Co-authored-by: Nikolas Klauser <[email protected]>
This commit adds move constructor and move assignment to
`exception_ptr`. Adding those operators allows us to avoid unnecessary
calls to `__cxa_{inc,dec}rement_refcount`.
Performance results:
```
Benchmark Baseline Candidate Difference % Difference
------------------------------- ---------- ----------- ------------ --------------
bm_nonnull_exception_ptr 52.22 40.92 -11.31 -21.65
bm_null_exception_ptr 31.41 23.29 -8.12 -25.85
bm_optimized_null_exception_ptr 28.69 20.50 -8.19 -28.55
```
This commit does not add a `swap` specialization. Thanks to the added
move-assignment, we already save a couple of increments/decrements also
in the default `swap` implementation. The default `swap` is still not
perfect, as it calls the desctructor on `tmp`. As soon as we also
inlined the `~exception_ptr` destructor fast-path for `__ptr ==
nullptr`, the optimizer should be able to optimize the default `swap`
just as well as a specialized `swap`, though.
b595542 to
154c286
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically LGTM, just some nits.
| friend _LIBCPP_HIDE_FROM_ABI void swap(exception_ptr& __x, exception_ptr& __y) _NOEXCEPT { | ||
| void* __tmp = __x.__ptr_; | ||
| __x.__ptr_ = __y.__ptr_; | ||
| __y.__ptr_ = __tmp; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably not be a hidden friend. Otherwise std::swap(ep1, ep2) won't use this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to change it, but just for my education: isn't std::swap(ep1, ep2) discouraged, anyway?
Afaik, the recommended usage pattern is
using std::swap;
swap(ep1, ep2);
such that ADL works and we have a fallback to std::swap in case there is no specialization.
I thought that using hidden friends would be the new best practice since it leads to a smaller overload set and more readable error messages. E.g., move_only_function::swap, jthread::swap, expected::swap and some others are also specified as a hidden friend.
Happy to change it to a non-hidden friend, though - please confirm that I should still do so to avoid unnecessary forth-and-back 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is recommended to use ADL, but that doesn't mean people do it. For that reason alone IMO it's a good idea to not make it a hidden friend. FWIW I don't think http://eel.is/c++draft/hidden.friends actually has any normative effect except that implementations aren't required to provide it through qualified lookup. How would anybody know the difference between the generic swap and the specialized version?
AFAIK the reason for introducing it were overloaded operators, not swap.
|
I updated this commit to use implement the move-assignment using Is that what you had in mind? Benchmark results (original implementation) Benchmark results (swap-based implementation) Interpretation of benchmark results I consider the In particular The As soon as we inline the destructor, this regression should disappear again. As such, I think we can live with that temporary regression - WDYT? |
Yes, exactly.
Yeah, I think that's fine. Also, please update the commit message to mention that we introduce |
👍 then I think we have high-level alignment on this PR. The next step will be to polish it for final review.
I will do so after #164278 shipped, so I won't have to repeatedly rebase this PR and redo the measurements anymore One more question: |
Yeah, that should fix the CI. |
This commit adds move constructor and move assignment to
exception_ptr. Adding those operators allows us to avoid unnecessarycalls to
__cxa_{inc,dec}rement_refcount.Performance results:
This commit does not add a
swapspecialization. Thanks to the addedmove-assignment, we already save a couple of increments/decrements also
in the default
swapimplementation. The defaultswapis still notperfect, as it calls the desctructor on
tmp. As soon as we alsoinlined the
~exception_ptrdestructor fast-path for__ptr == nullptr, the optimizer should be able to optimize the defaultswapjust as well as a specialized
swap, though.