-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc++] Add benchmarks for partitioning algorithms #127324
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
Conversation
@llvm/pr-subscribers-libcxx Author: Louis Dionne (ldionne) ChangesThis patch adds benchmarks for std::partition, is_partitioned, etc and their ranges:: variants. Patch is 20.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/127324.diff 6 Files Affected:
diff --git a/libcxx/test/benchmarks/algorithms/algorithms.partition_point.bench.cpp b/libcxx/test/benchmarks/algorithms/algorithms.partition_point.bench.cpp
deleted file mode 100644
index e0bd7e36f78ad..0000000000000
--- a/libcxx/test/benchmarks/algorithms/algorithms.partition_point.bench.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 <array>
-#include <cassert>
-#include <cstdint>
-#include <tuple>
-#include <vector>
-
-#include "benchmark/benchmark.h"
-
-#include "../CartesianBenchmarks.h"
-#include "../GenerateInput.h"
-
-namespace {
-
-template <typename I, typename N>
-std::array<I, 10> every_10th_percentile_N(I first, N n) {
- N step = n / 10;
- std::array<I, 10> res;
-
- for (size_t i = 0; i < 10; ++i) {
- res[i] = first;
- std::advance(first, step);
- }
-
- return res;
-}
-
-template <class IntT>
-struct TestIntBase {
- static std::vector<IntT> generateInput(size_t size) {
- std::vector<IntT> Res(size);
- std::generate(Res.begin(), Res.end(), [] { return getRandomInteger<IntT>(0, std::numeric_limits<IntT>::max()); });
- return Res;
- }
-};
-
-struct TestInt32 : TestIntBase<std::int32_t> {
- static constexpr const char* Name = "TestInt32";
-};
-
-struct TestInt64 : TestIntBase<std::int64_t> {
- static constexpr const char* Name = "TestInt64";
-};
-
-struct TestUint32 : TestIntBase<std::uint32_t> {
- static constexpr const char* Name = "TestUint32";
-};
-
-struct TestMediumString {
- static constexpr const char* Name = "TestMediumString";
- static constexpr size_t StringSize = 32;
-
- static std::vector<std::string> generateInput(size_t size) {
- std::vector<std::string> Res(size);
- std::generate(Res.begin(), Res.end(), [] { return getRandomString(StringSize); });
- return Res;
- }
-};
-
-using AllTestTypes = std::tuple<TestInt32, TestInt64, TestUint32, TestMediumString>;
-
-struct LowerBoundAlg {
- template <class I, class V>
- I operator()(I first, I last, const V& value) const {
- return std::lower_bound(first, last, value);
- }
-
- static constexpr const char* Name = "LowerBoundAlg";
-};
-
-struct UpperBoundAlg {
- template <class I, class V>
- I operator()(I first, I last, const V& value) const {
- return std::upper_bound(first, last, value);
- }
-
- static constexpr const char* Name = "UpperBoundAlg";
-};
-
-struct EqualRangeAlg {
- template <class I, class V>
- std::pair<I, I> operator()(I first, I last, const V& value) const {
- return std::equal_range(first, last, value);
- }
-
- static constexpr const char* Name = "EqualRangeAlg";
-};
-
-using AllAlgs = std::tuple<LowerBoundAlg, UpperBoundAlg, EqualRangeAlg>;
-
-template <class Alg, class TestType>
-struct PartitionPointBench {
- size_t Quantity;
-
- std::string name() const {
- return std::string("PartitionPointBench_") + Alg::Name + "_" + TestType::Name + '/' + std::to_string(Quantity);
- }
-
- void run(benchmark::State& state) const {
- auto Data = TestType::generateInput(Quantity);
- std::sort(Data.begin(), Data.end());
- auto Every10Percentile = every_10th_percentile_N(Data.begin(), Data.size());
-
- for (auto _ : state) {
- for (auto Test : Every10Percentile)
- benchmark::DoNotOptimize(Alg{}(Data.begin(), Data.end(), *Test));
- }
- }
-};
-
-} // namespace
-
-int main(int argc, char** argv) {
- benchmark::Initialize(&argc, argv);
- if (benchmark::ReportUnrecognizedArguments(argc, argv))
- return 1;
-
- const std::vector<size_t> Quantities = {1 << 8, 1 << 10, 1 << 20};
- makeCartesianProductBenchmark<PartitionPointBench, AllAlgs, AllTestTypes>(Quantities);
- benchmark::RunSpecifiedBenchmarks();
-}
diff --git a/libcxx/test/benchmarks/algorithms/partitions/is_partitioned.bench.cpp b/libcxx/test/benchmarks/algorithms/partitions/is_partitioned.bench.cpp
new file mode 100644
index 0000000000000..55f8f7dcb990a
--- /dev/null
+++ b/libcxx/test/benchmarks/algorithms/partitions/is_partitioned.bench.cpp
@@ -0,0 +1,94 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <cassert>
+#include <deque>
+#include <iterator>
+#include <list>
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "../../GenerateInput.h"
+
+auto compute_median(auto first, auto last) {
+ std::vector v(first, last);
+ auto middle = v.begin() + v.size() / 2;
+ std::nth_element(v.begin(), middle, v.end());
+ return *middle;
+}
+
+template <class Container, bool Partitioned, class Operation>
+void bm(std::string operation_name, Operation is_partitioned) {
+ auto bench = [is_partitioned](auto& st) {
+ auto const size = st.range(0);
+ using ValueType = typename Container::value_type;
+ Container c;
+ std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
+
+ // Partition the container in two equally-sized halves, ensuring the median
+ // value appears in the left half. Note that the median value isn't located
+ // in the middle -- this isn't std::nth_element.
+ ValueType median = compute_median(c.begin(), c.end());
+ auto pred = [median](auto const& element) { return element <= median; };
+ std::partition(c.begin(), c.end(), pred);
+ assert(std::is_partitioned(c.begin(), c.end(), pred));
+
+ if constexpr (!Partitioned) {
+ // De-partition the container by swapping the element containing the median
+ // value with the last one.
+ auto median_it = std::find(c.begin(), c.end(), median);
+ auto last_it = std::next(c.begin(), c.size() - 1);
+ std::iter_swap(median_it, last_it);
+ assert(!std::is_partitioned(c.begin(), c.end(), pred));
+ }
+
+ for ([[maybe_unused]] auto _ : st) {
+ auto result = is_partitioned(c.begin(), c.end(), pred);
+ benchmark::DoNotOptimize(result);
+ benchmark::DoNotOptimize(c);
+ benchmark::ClobberMemory();
+ }
+ };
+ benchmark::RegisterBenchmark(operation_name, bench)->Arg(32)->Arg(1024)->Arg(8192);
+}
+
+int main(int argc, char** argv) {
+ auto std_is_partitioned = [](auto first, auto last, auto pred) { return std::is_partitioned(first, last, pred); };
+ auto ranges_is_partitioned = [](auto first, auto last, auto pred) {
+ return std::ranges::is_partitioned(first, last, pred);
+ };
+
+ // std::is_partitioned
+ bm<std::vector<int>, true>("std::is_partitioned(vector<int>) (partitioned)", std_is_partitioned);
+ bm<std::vector<int>, false>("std::is_partitioned(vector<int>) (not partitioned)", std_is_partitioned);
+
+ bm<std::deque<int>, true>("std::is_partitioned(deque<int>) (partitioned)", std_is_partitioned);
+ bm<std::deque<int>, false>("std::is_partitioned(deque<int>) (not partitioned)", std_is_partitioned);
+
+ bm<std::list<int>, true>("std::is_partitioned(list<int>) (partitioned)", std_is_partitioned);
+ bm<std::list<int>, false>("std::is_partitioned(list<int>) (not partitioned)", std_is_partitioned);
+
+ // ranges::is_partitioned
+ bm<std::vector<int>, true>("ranges::is_partitioned(vector<int>) (partitioned)", ranges_is_partitioned);
+ bm<std::vector<int>, false>("ranges::is_partitioned(vector<int>) (not partitioned)", ranges_is_partitioned);
+
+ bm<std::deque<int>, true>("ranges::is_partitioned(deque<int>) (partitioned)", ranges_is_partitioned);
+ bm<std::deque<int>, false>("ranges::is_partitioned(deque<int>) (not partitioned)", ranges_is_partitioned);
+
+ bm<std::list<int>, true>("ranges::is_partitioned(list<int>) (partitioned)", ranges_is_partitioned);
+ bm<std::list<int>, false>("ranges::is_partitioned(list<int>) (not partitioned)", ranges_is_partitioned);
+
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+ return 0;
+}
diff --git a/libcxx/test/benchmarks/algorithms/partitions/partition.bench.cpp b/libcxx/test/benchmarks/algorithms/partitions/partition.bench.cpp
new file mode 100644
index 0000000000000..4e2cd3170fc6b
--- /dev/null
+++ b/libcxx/test/benchmarks/algorithms/partitions/partition.bench.cpp
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <deque>
+#include <iterator>
+#include <list>
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "../../GenerateInput.h"
+
+auto compute_median(auto first, auto last) {
+ std::vector v(first, last);
+ auto middle = v.begin() + v.size() / 2;
+ std::nth_element(v.begin(), middle, v.end());
+ return *middle;
+}
+
+template <class Container, class Operation>
+void bm(std::string operation_name, Operation partition) {
+ auto bench = [partition](auto& st) {
+ auto const size = st.range(0);
+ using ValueType = typename Container::value_type;
+ Container c;
+ std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
+
+ std::vector<ValueType> yes(size), no(size);
+ ValueType median = compute_median(c.begin(), c.end());
+ auto pred1 = [median](auto const& element) { return element < median; };
+ auto pred2 = [median](auto const& element) { return element > median; };
+ bool toggle = false;
+
+ for ([[maybe_unused]] auto _ : st) {
+ if (toggle) {
+ auto result = partition(c.begin(), c.end(), pred1);
+ benchmark::DoNotOptimize(result);
+ } else {
+ auto result = partition(c.begin(), c.end(), pred2);
+ benchmark::DoNotOptimize(result);
+ }
+ toggle = !toggle;
+
+ benchmark::DoNotOptimize(c);
+ benchmark::ClobberMemory();
+ }
+ };
+ benchmark::RegisterBenchmark(operation_name, bench)->Arg(32)->Arg(1024)->Arg(8192);
+}
+
+int main(int argc, char** argv) {
+ auto std_partition = [](auto first, auto last, auto pred) { return std::partition(first, last, pred); };
+ auto ranges_partition = [](auto first, auto last, auto pred) { return std::ranges::partition(first, last, pred); };
+
+ // std::partition
+ bm<std::vector<int>>("std::partition(vector<int>)", std_partition);
+ bm<std::deque<int>>("std::partition(deque<int>)", std_partition);
+ bm<std::list<int>>("std::partition(list<int>)", std_partition);
+
+ // ranges::partition
+ bm<std::vector<int>>("ranges::partition(vector<int>)", ranges_partition);
+ bm<std::deque<int>>("ranges::partition(deque<int>)", ranges_partition);
+ bm<std::list<int>>("ranges::partition(list<int>)", ranges_partition);
+
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+ return 0;
+}
diff --git a/libcxx/test/benchmarks/algorithms/partitions/partition_copy.bench.cpp b/libcxx/test/benchmarks/algorithms/partitions/partition_copy.bench.cpp
new file mode 100644
index 0000000000000..68f3fa027169e
--- /dev/null
+++ b/libcxx/test/benchmarks/algorithms/partitions/partition_copy.bench.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <deque>
+#include <iterator>
+#include <list>
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "../../GenerateInput.h"
+
+auto compute_median(auto first, auto last) {
+ std::vector v(first, last);
+ auto middle = v.begin() + v.size() / 2;
+ std::nth_element(v.begin(), middle, v.end());
+ return *middle;
+}
+
+template <class Container, class Operation>
+void bm(std::string operation_name, Operation partition_copy) {
+ auto bench = [partition_copy](auto& st) {
+ auto const size = st.range(0);
+ using ValueType = typename Container::value_type;
+ Container c;
+ std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
+
+ std::vector<ValueType> yes(size), no(size);
+ ValueType median = compute_median(c.begin(), c.end());
+ auto pred = [median](auto const& element) { return element < median; };
+
+ for ([[maybe_unused]] auto _ : st) {
+ auto result = partition_copy(c.begin(), c.end(), yes.begin(), no.begin(), pred);
+ benchmark::DoNotOptimize(yes);
+ benchmark::DoNotOptimize(no);
+ benchmark::DoNotOptimize(result);
+ benchmark::DoNotOptimize(c);
+ benchmark::ClobberMemory();
+ }
+ };
+ benchmark::RegisterBenchmark(operation_name, bench)->Arg(32)->Arg(1024)->Arg(8192);
+}
+
+int main(int argc, char** argv) {
+ auto std_partition_copy = [](auto first, auto last, auto out_yes, auto out_no, auto pred) {
+ return std::partition_copy(first, last, out_yes, out_no, pred);
+ };
+ auto ranges_partition_copy = [](auto first, auto last, auto out_yes, auto out_no, auto pred) {
+ return std::ranges::partition_copy(first, last, out_yes, out_no, pred);
+ };
+
+ // std::partition_copy
+ bm<std::vector<int>>("std::partition_copy(vector<int>)", std_partition_copy);
+ bm<std::deque<int>>("std::partition_copy(deque<int>)", std_partition_copy);
+ bm<std::list<int>>("std::partition_copy(list<int>)", std_partition_copy);
+
+ // ranges::partition_copy
+ bm<std::vector<int>>("ranges::partition_copy(vector<int>)", ranges_partition_copy);
+ bm<std::deque<int>>("ranges::partition_copy(deque<int>)", ranges_partition_copy);
+ bm<std::list<int>>("ranges::partition_copy(list<int>)", ranges_partition_copy);
+
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+ return 0;
+}
diff --git a/libcxx/test/benchmarks/algorithms/partitions/partition_point.bench.cpp b/libcxx/test/benchmarks/algorithms/partitions/partition_point.bench.cpp
new file mode 100644
index 0000000000000..da810a59bff5b
--- /dev/null
+++ b/libcxx/test/benchmarks/algorithms/partitions/partition_point.bench.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <deque>
+#include <iterator>
+#include <list>
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "../../GenerateInput.h"
+
+auto compute_median(auto first, auto last) {
+ std::vector v(first, last);
+ auto middle = v.begin() + v.size() / 2;
+ std::nth_element(v.begin(), middle, v.end());
+ return *middle;
+}
+
+template <class Container, class Operation>
+void bm(std::string operation_name, Operation partition_point) {
+ auto bench = [partition_point](auto& st) {
+ auto const size = st.range(0);
+ using ValueType = typename Container::value_type;
+ Container c;
+ std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
+
+ // Partition the container in two equally-sized halves. Based on experimentation, the running
+ // time of the algorithm doesn't change much depending on the size of the halves.
+ ValueType median = compute_median(c.begin(), c.end());
+ auto pred = [median](auto const& element) { return element < median; };
+ std::partition(c.begin(), c.end(), pred);
+ assert(std::is_partitioned(c.begin(), c.end(), pred));
+
+ for ([[maybe_unused]] auto _ : st) {
+ auto result = partition_point(c.begin(), c.end(), pred);
+ benchmark::DoNotOptimize(result);
+ benchmark::DoNotOptimize(c);
+ benchmark::ClobberMemory();
+ }
+ };
+ benchmark::RegisterBenchmark(operation_name, bench)->Arg(32)->Arg(1024)->Arg(8192);
+}
+
+int main(int argc, char** argv) {
+ auto std_partition_point = [](auto first, auto last, auto pred) { return std::partition_point(first, last, pred); };
+ auto ranges_partition_point = [](auto first, auto last, auto pred) {
+ return std::ranges::partition_point(first, last, pred);
+ };
+
+ // std::partition_point
+ bm<std::vector<int>>("std::partition_point(vector<int>)", std_partition_point);
+ bm<std::deque<int>>("std::partition_point(deque<int>)", std_partition_point);
+ bm<std::list<int>>("std::partition_point(list<int>)", std_partition_point);
+
+ // ranges::partition_point
+ bm<std::vector<int>>("ranges::partition_point(vector<int>)", ranges_partition_point);
+ bm<std::deque<int>>("ranges::partition_point(deque<int>)", ranges_partition_point);
+ bm<std::list<int>>("ranges::partition_point(list<int>)", ranges_partition_point);
+
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+ return 0;
+}
diff --git a/libcxx/test/benchmarks/algorithms/partitions/stable_partition.bench.cpp b/libcxx/test/benchmarks/algorithms/partitions/stable_partition.bench.cpp
new file mode 100644
index 0000000000000..60e723a3a29d8
--- /dev/null
+++ b/libcxx/test/benchmarks/algorithms/partitions/stable_partition.bench.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <deque>
+#include <iterator>
+#include <list>
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "../../GenerateInput.h"
+
+auto compute_median(auto first, auto last) {
+ std::vector v(first, last);
+ auto middle = v.begin() + v.size() / 2;
+ std::nth_element(v.begin(), middle, v.end());
+ return *middle;
+}
+
+template <class Container, class Operation>
+void bm(std::string operation_name, Operation stable_partition) {
+ auto bench = [stable_partition](auto& st) {
+ auto const size = st.range(0);
+ using ValueType = typename Container::value_type;
+ Container c;
+ std::generate_n(std::back_inserter(c), size, [] { return Generate<ValueType>::random(); });
+
+ std::vector<ValueType> yes(size), no(size);
+ ValueType median = compute_median(c.begin(), c.end());
+ auto pred1 = [median](auto const& element) { return element < median; };
+ auto pred2 = [median](auto const& element) { return element > median; };
+ bool toggle = false;
+
+ for ([[maybe_unused]] auto _ : st) {
+ if (toggle) {
+ auto result = stable_partition(c.begin(), c.end(), pred1);
+ benchmark::DoNotOptimize(result);
+ } else {
+ auto result = stable_partition(c.begin(), c.end(), pred2);
+ benchmark::DoNotOptimize(result);
+ }
+ toggle = !toggle;
+
+ benchmark::DoNotOptimize(c);
+ benchmark::ClobberMemory();
+ }
+ };
+ benchmark::RegisterBenchmark(operation_name, bench)->Arg(32)->Arg(1024)->Arg(8192);
+}
+
+int main(int argc, char** argv) {
+ auto std_stable_partition = [](auto first, auto last, auto pr...
[truncated]
|
libcxx/test/benchmarks/algorithms/partitions/is_partitioned.bench.cpp
Outdated
Show resolved
Hide resolved
libcxx/test/benchmarks/algorithms/partitions/partition.bench.cpp
Outdated
Show resolved
Hide resolved
libcxx/test/benchmarks/algorithms/partitions/partition_copy.bench.cpp
Outdated
Show resolved
Hide resolved
This patch adds benchmarks for std::partition, is_partitioned, etc and their ranges:: variants.
1770cf0
to
32baaa9
Compare
I'll merge this once CI is finished since @philnik777 and I have went over it pretty extensively, but I will make another pass with him when he's back from vacation for due diligence. |
This patch adds benchmarks for std::partition, is_partitioned, etc and their ranges:: variants.