Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions libcxx/test/benchmarks/exception_ptr.bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,49 @@ void bm_make_exception_ptr(benchmark::State& state) {
}
BENCHMARK(bm_make_exception_ptr)->ThreadRange(1, 8);

static bool exception_ptr_moves_copies_swap(std::exception_ptr p1) {
// Taken from https://github.com/llvm/llvm-project/issues/44892
std::exception_ptr p2(p1); // Copy constructor
std::exception_ptr p3(std::move(p2)); // Move constructor
p2 = std::move(p1); // Move assignment
p1 = p2; // Copy assignment
swap(p1, p2); // Swap
// Comparisons against nullptr. The overhead from creating temporary `exception_ptr`
// instances should be optimized out.
bool is_null = p1 == nullptr && nullptr == p2;
bool is_equal = p1 == p2; // Comparison
return is_null && is_equal;
}

void bm_nonnull_exception_ptr(benchmark::State& state) {
std::exception_ptr excptr = std::make_exception_ptr(42);
for (auto _ : state) {
benchmark::DoNotOptimize(excptr);
benchmark::DoNotOptimize(exception_ptr_moves_copies_swap(excptr));
}
}
BENCHMARK(bm_nonnull_exception_ptr);

void bm_null_exception_ptr(benchmark::State& state) {
std::exception_ptr excptr;
for (auto _ : state) {
// All of the `exception_ptr_noops` are no-ops but the optimizer
// cannot optimize them away, because the `DoNotOptimize` calls
// prevent the optimizer from doing so.
benchmark::DoNotOptimize(excptr);
benchmark::DoNotOptimize(exception_ptr_moves_copies_swap(excptr));
}
}
BENCHMARK(bm_null_exception_ptr);

void bm_optimized_null_exception_ptr(benchmark::State& state) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the purpose of these benchmarks? Perhaps a comment above each would be helpful.

Are these benchmarks going to be useful going forward (e.g. preventing regressions), or are they only useful in the context of comparing stuff as you're actively working on the subsequent patch? bm_optimized_null_exception_ptr seems like it might only be useful to establish a baseline, but might not provide value on its own (but I don't fully understand its purpose yet)?

Copy link
Member Author

@vogelsgesang vogelsgesang Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the purpose of these benchmarks? Perhaps a comment above each would be helpful.

Updated the comments

Are these benchmarks going to be useful going forward (e.g. preventing regressions), or are they only useful in the context of comparing stuff as you're actively working on the subsequent patch?

I think they might also be useful in the future. E.g., my work in #162773 did not cover the MSVC ABI, because LLVM doesn't have a truly native MSVC exception_ptr implementation, yet. As soon as somebody revisits the Windows exception_ptr implementation, those benchmarks will be useful again.

bm_optimized_null_exception_ptr seems like it might only be useful to establish a baseline, but might not provide value on its own (but I don't fully understand its purpose yet)?

Both bm_optimized_null_exception_ptr and bm_null_exception_ptr check the performance for empty exception_ptrs. The main difference is that for the "optimized" variant, the compiler can proof that the argument passed to exception_ptr_moves_copies_swap is a nullptr, while for the bm_null_exception_ptr the DoNotOptimize(excptr) leaks the exception pointer and the compiler can hence no longer proof that the exception_ptr is empty and must add runtime checks.

The benchmark results for bm_optimized_null_exception_ptr and bm_null_exception_ptr differ slightly, as exemplified by the benchmark result from #164281 (adding move ctor & assignment):

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

Even with #162773 (which inlines much more aggressively, but might never ship in its current state), there still is a slight difference between bm_null_exception_ptr and bm_optimized_null_exception_ptr:

Benchmark                          Baseline    Candidate    Difference    % Difference
-------------------------------  ----------  -----------  ------------  --------------
bm_nonnull_exception_ptr              40.92        33.69         -7.23          -17.66
bm_null_exception_ptr                 23.29         1.21        -22.07          -94.79
bm_optimized_null_exception_ptr       20.50         0.70        -19.80          -96.61

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the additional explanation and for updating the comments!

for (auto _ : state) {
// All of the `exception_ptr_noops` are no-ops because
// the exception_ptr is empty. Hence, the compiler should
// be able to optimize them very aggressively.
benchmark::DoNotOptimize(exception_ptr_moves_copies_swap(std::exception_ptr{nullptr}));
}
}
BENCHMARK(bm_optimized_null_exception_ptr);

BENCHMARK_MAIN();
Loading