[libcxx-commits] [libcxx] [libc++] Rewrite the set_intersection benchmark without CartesianBenchmarks (PR #214302)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Aug 10 05:06:32 PDT 2026
================
@@ -0,0 +1,209 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <algorithm>
+#include <cstddef>
+#include <deque>
+#include <iterator>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "../../GenerateInput.h"
+
+// Each benchmarking scenario produces the two sorted ranges to intersect, with `n` the size of the
+// larger of the two. The size of the smaller range is derived from `n`, and the ratio between the
+// two appears in the benchmark's name. That ratio is used to exercise implementation choices that
+// make a difference when the two ranges have very different sizes.
+
+// Generate `n` random values and return two independently allocated sorted copies of them.
+//
+// The copy is made before sorting, so that the two copies don't end up with their out-of-line
+// storage laid out in the same order.
+template <class T>
+std::pair<std::vector<T>, std::vector<T>> two_sorted_copies(std::size_t n) {
+ std::vector<T> a(n);
+ std::generate_n(a.begin(), n, [] { return Generate<T>::random(); });
+ std::vector<T> b = a;
+ std::sort(a.begin(), a.end());
+ std::sort(b.begin(), b.end());
+ return std::make_pair(std::move(a), std::move(b));
+}
+
+// Generate two ranges where the larger range holds `n` elements and the smaller one holds every `ratio`th
+// element of it, so the whole smaller range matches and the matches are spread evenly through the larger
+// one. Whether searching within the larger range beats scanning it depends on how far apart the matches
+// are, which is why this scenario is instantiated with several ratios. With a ratio of 1 the two ranges
+// are identical, which is the worst case for the number of elements written to the output.
+template <class T>
+std::pair<std::vector<T>, std::vector<T>> spread(std::size_t n, std::size_t ratio) {
+ auto [large, sampled] = two_sorted_copies<T>(n);
+ std::vector<T> small;
+ small.reserve(n / ratio + 1);
+ for (std::size_t i = 0; i < n; i += ratio)
+ small.push_back(std::move(sampled[i]));
+ return std::make_pair(std::move(large), std::move(small));
+}
+
+int main(int argc, char** argv) {
+ auto bm = []<class Container>(std::string name, auto generate) {
+ return benchmark::RegisterBenchmark(name, [generate](auto& st) {
+ using ValueType = typename Container::value_type;
+ std::size_t const size = st.range(0);
+
+ // Generate the values, then shuffle them so we don't insert into the containers in sorted order.
+ // Otherwise, node-based containers end up with an artificially good memory layout.
+ auto [values1, values2] = generate(std::type_identity<ValueType>{}, size);
+ std::shuffle(values1.begin(), values1.end(), getRandomEngine());
+ std::shuffle(values2.begin(), values2.end(), getRandomEngine());
----------------
ldionne wrote:
We shuffle, then insert into the containers so that the end sorted order doesn't match the insertion order (which would be reflected in the allocation order of nodes), and then we sort if we need to.
However, this makes me realize that I don't think we have to jump through as many hoops in e.g. `two_sorted_copies` to ensure that allocation order doesn't match iteration order, since we do it here.
https://github.com/llvm/llvm-project/pull/214302
More information about the libcxx-commits
mailing list