[libc++] Add benchmarks for std::binary_search and std::equal_range (#207268)
The found and not-found paths are measured as separate benchmarks. For equal_range we additionally cover a unique match, a large equal range, and an absent key. As a drive-by, add a predicate variation to the existing lower_bound and upper_bound benchmarks.
This commit is contained in:
parent
a16f3a0941
commit
0a933be62e
3 changed files with 309 additions and 55 deletions
|
|
@ -0,0 +1,121 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 <forward_list>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "../../GenerateInput.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
auto std_binary_search = [](auto first, auto last, auto const& value) {
|
||||
return std::binary_search(first, last, value);
|
||||
};
|
||||
auto std_binary_search_pred = [](auto first, auto last, auto const& value) {
|
||||
return std::binary_search(first, last, value, [](auto x, auto y) { return x < y; });
|
||||
};
|
||||
|
||||
// Benchmark binary_search looking up a random key that is present in the sequence.
|
||||
{
|
||||
auto bm = []<class Container>(std::string name, auto binary_search) {
|
||||
benchmark::RegisterBenchmark(
|
||||
name,
|
||||
[binary_search](auto& st) {
|
||||
using ValueType = typename Container::value_type;
|
||||
std::size_t const size = st.range(0);
|
||||
|
||||
// Random sorted data
|
||||
std::vector<ValueType> data(size);
|
||||
std::generate_n(data.begin(), size, &Generate<ValueType>::random);
|
||||
std::sort(data.begin(), data.end());
|
||||
|
||||
// Precompute a bunch of random keys.
|
||||
std::vector<ValueType> keys(data);
|
||||
std::shuffle(keys.begin(), keys.end(), getRandomEngine());
|
||||
|
||||
Container c(data.begin(), data.end());
|
||||
std::size_t pos = 0;
|
||||
for (auto _ : st) {
|
||||
benchmark::DoNotOptimize(c);
|
||||
bool result = binary_search(c.begin(), c.end(), keys[pos]);
|
||||
benchmark::DoNotOptimize(result);
|
||||
if (++pos == keys.size())
|
||||
pos = 0;
|
||||
}
|
||||
})
|
||||
->Arg(8)
|
||||
->Arg(100)
|
||||
->Arg(8192);
|
||||
};
|
||||
// clang-format off
|
||||
bm.operator()<std::vector<int>>("std::binary_search(vector<int>) (present)", std_binary_search);
|
||||
bm.operator()<std::deque<int>>("std::binary_search(deque<int>) (present)", std_binary_search);
|
||||
bm.operator()<std::list<int>>("std::binary_search(list<int>) (present)", std_binary_search);
|
||||
bm.operator()<std::forward_list<int>>("std::binary_search(forward_list<int>) (present)", std_binary_search);
|
||||
|
||||
bm.operator()<std::vector<int>>("std::binary_search(vector<int>, pred) (present)", std_binary_search_pred);
|
||||
bm.operator()<std::deque<int>>("std::binary_search(deque<int>, pred) (present)", std_binary_search_pred);
|
||||
bm.operator()<std::list<int>>("std::binary_search(list<int>, pred) (present)", std_binary_search_pred);
|
||||
bm.operator()<std::forward_list<int>>("std::binary_search(forward_list<int>, pred) (present)", std_binary_search_pred);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// Benchmark binary_search looking up a key that is absent from the sequence.
|
||||
{
|
||||
auto bm = []<class Container>(std::string name, auto binary_search) {
|
||||
benchmark::RegisterBenchmark(
|
||||
name,
|
||||
[binary_search](auto& st) {
|
||||
using ValueType = typename Container::value_type;
|
||||
std::size_t const size = st.range(0);
|
||||
|
||||
// Random sorted data
|
||||
std::vector<ValueType> data(size);
|
||||
std::generate_n(data.begin(), size, &Generate<ValueType>::random);
|
||||
std::sort(data.begin(), data.end());
|
||||
|
||||
// Find a key that isn't in the sequence
|
||||
ValueType absent = Generate<ValueType>::random();
|
||||
while (std::find(data.begin(), data.end(), absent) != data.end())
|
||||
absent = Generate<ValueType>::random();
|
||||
|
||||
Container c(data.begin(), data.end());
|
||||
for (auto _ : st) {
|
||||
benchmark::DoNotOptimize(c);
|
||||
bool result = binary_search(c.begin(), c.end(), absent);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
})
|
||||
->Arg(8)
|
||||
->Arg(100)
|
||||
->Arg(8192);
|
||||
};
|
||||
// clang-format off
|
||||
bm.operator()<std::vector<int>>("std::binary_search(vector<int>) (absent)", std_binary_search);
|
||||
bm.operator()<std::deque<int>>("std::binary_search(deque<int>) (absent)", std_binary_search);
|
||||
bm.operator()<std::list<int>>("std::binary_search(list<int>) (absent)", std_binary_search);
|
||||
bm.operator()<std::forward_list<int>>("std::binary_search(forward_list<int>) (absent)", std_binary_search);
|
||||
|
||||
bm.operator()<std::vector<int>>("std::binary_search(vector<int>, pred) (absent)", std_binary_search_pred);
|
||||
bm.operator()<std::deque<int>>("std::binary_search(deque<int>, pred) (absent)", std_binary_search_pred);
|
||||
bm.operator()<std::list<int>>("std::binary_search(list<int>, pred) (absent)", std_binary_search_pred);
|
||||
bm.operator()<std::forward_list<int>>("std::binary_search(forward_list<int>, pred) (absent)", std_binary_search_pred);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
benchmark::Initialize(&argc, argv);
|
||||
benchmark::RunSpecifiedBenchmarks();
|
||||
benchmark::Shutdown();
|
||||
return 0;
|
||||
}
|
||||
133
libcxx/test/benchmarks/algorithms/sorting/equal_range.bench.cpp
Normal file
133
libcxx/test/benchmarks/algorithms/sorting/equal_range.bench.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 <forward_list>
|
||||
#include <list>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
auto std_equal_range = [](auto first, auto last, auto const& value) { return std::equal_range(first, last, value); };
|
||||
auto std_equal_range_pred = [](auto first, auto last, auto const& value) {
|
||||
return std::equal_range(first, last, value, [](auto x, auto y) { return x < y; });
|
||||
};
|
||||
|
||||
// The range we find is a single element.
|
||||
{
|
||||
auto bm = []<class Container>(std::string name, auto equal_range) {
|
||||
benchmark::RegisterBenchmark(
|
||||
name,
|
||||
[equal_range](auto& st) {
|
||||
std::size_t const size = st.range(0);
|
||||
std::vector<int> data(size);
|
||||
std::iota(data.begin(), data.end(), 0);
|
||||
int const key = static_cast<int>(size / 2);
|
||||
|
||||
Container c(data.begin(), data.end());
|
||||
for (auto _ : st) {
|
||||
benchmark::DoNotOptimize(c);
|
||||
auto result = equal_range(c.begin(), c.end(), key);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
})
|
||||
->Arg(8)
|
||||
->Arg(100)
|
||||
->Arg(8192);
|
||||
};
|
||||
bm.operator()<std::vector<int>>("std::equal_range(vector<int>) (unique)", std_equal_range);
|
||||
bm.operator()<std::deque<int>>("std::equal_range(deque<int>) (unique)", std_equal_range);
|
||||
bm.operator()<std::list<int>>("std::equal_range(list<int>) (unique)", std_equal_range);
|
||||
bm.operator()<std::forward_list<int>>("std::equal_range(forward_list<int>) (unique)", std_equal_range);
|
||||
bm.operator()<std::vector<int>>("std::equal_range(vector<int>, pred) (unique)", std_equal_range_pred);
|
||||
bm.operator()<std::deque<int>>("std::equal_range(deque<int>, pred) (unique)", std_equal_range_pred);
|
||||
bm.operator()<std::list<int>>("std::equal_range(list<int>, pred) (unique)", std_equal_range_pred);
|
||||
bm.operator()<std::forward_list<int>>("std::equal_range(forward_list<int>, pred) (unique)", std_equal_range_pred);
|
||||
}
|
||||
|
||||
// The range we find is a large part of the entire range.
|
||||
// Data looks like [0, 1, 2, 2, 2, 2, 2, 7, 8, 9], we match the 2's.
|
||||
{
|
||||
auto bm = []<class Container>(std::string name, auto equal_range) {
|
||||
benchmark::RegisterBenchmark(
|
||||
name,
|
||||
[equal_range](auto& st) {
|
||||
std::size_t const size = st.range(0);
|
||||
std::size_t const subrange_size = size / 2;
|
||||
std::size_t const left_flank = (size - subrange_size) / 2;
|
||||
std::vector<int> data(size);
|
||||
std::iota(data.begin(), data.end(), 0); // [0, 1, 2, 3, ..., 8, 9]
|
||||
int const key = data[left_flank];
|
||||
std::fill_n(data.begin() + left_flank, subrange_size,
|
||||
key); // [0, 1, 2, 3, 3, 3, 3, ..., 8, 9]
|
||||
|
||||
Container c(data.begin(), data.end());
|
||||
for (auto _ : st) {
|
||||
benchmark::DoNotOptimize(c);
|
||||
auto result = equal_range(c.begin(), c.end(), key);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
})
|
||||
->Arg(8)
|
||||
->Arg(100)
|
||||
->Arg(8192);
|
||||
};
|
||||
bm.operator()<std::vector<int>>("std::equal_range(vector<int>) (large range)", std_equal_range);
|
||||
bm.operator()<std::deque<int>>("std::equal_range(deque<int>) (large range)", std_equal_range);
|
||||
bm.operator()<std::list<int>>("std::equal_range(list<int>) (large range)", std_equal_range);
|
||||
bm.operator()<std::forward_list<int>>("std::equal_range(forward_list<int>) (large range)", std_equal_range);
|
||||
bm.operator()<std::vector<int>>("std::equal_range(vector<int>, pred) (large range)", std_equal_range_pred);
|
||||
bm.operator()<std::deque<int>>("std::equal_range(deque<int>, pred) (large range)", std_equal_range_pred);
|
||||
bm.operator()<std::list<int>>("std::equal_range(list<int>, pred) (large range)", std_equal_range_pred);
|
||||
bm.operator()<std::forward_list<int>>(
|
||||
"std::equal_range(forward_list<int>, pred) (large range)", std_equal_range_pred);
|
||||
}
|
||||
|
||||
// The searched-for value is not present, so the found range is empty.
|
||||
{
|
||||
auto bm = []<class Container>(std::string name, auto equal_range) {
|
||||
benchmark::RegisterBenchmark(
|
||||
name,
|
||||
[equal_range](auto& st) {
|
||||
std::size_t const size = st.range(0);
|
||||
std::vector<int> data(size);
|
||||
std::iota(data.begin(), data.end(), 0);
|
||||
int const key = static_cast<int>(size); // one past the last element
|
||||
|
||||
Container c(data.begin(), data.end());
|
||||
for (auto _ : st) {
|
||||
benchmark::DoNotOptimize(c);
|
||||
auto result = equal_range(c.begin(), c.end(), key);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
})
|
||||
->Arg(8)
|
||||
->Arg(100)
|
||||
->Arg(8192);
|
||||
};
|
||||
bm.operator()<std::vector<int>>("std::equal_range(vector<int>) (absent)", std_equal_range);
|
||||
bm.operator()<std::deque<int>>("std::equal_range(deque<int>) (absent)", std_equal_range);
|
||||
bm.operator()<std::list<int>>("std::equal_range(list<int>) (absent)", std_equal_range);
|
||||
bm.operator()<std::forward_list<int>>("std::equal_range(forward_list<int>) (absent)", std_equal_range);
|
||||
bm.operator()<std::vector<int>>("std::equal_range(vector<int>, pred) (absent)", std_equal_range_pred);
|
||||
bm.operator()<std::deque<int>>("std::equal_range(deque<int>, pred) (absent)", std_equal_range_pred);
|
||||
bm.operator()<std::list<int>>("std::equal_range(list<int>, pred) (absent)", std_equal_range_pred);
|
||||
bm.operator()<std::forward_list<int>>("std::equal_range(forward_list<int>, pred) (absent)", std_equal_range_pred);
|
||||
}
|
||||
|
||||
benchmark::Initialize(&argc, argv);
|
||||
benchmark::RunSpecifiedBenchmarks();
|
||||
benchmark::Shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -6,78 +6,78 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
|
||||
// UNSUPPORTED: c++03, c++11, c++14, c++17
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "../../GenerateInput.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
{
|
||||
auto bm = []<class Container>(benchmark::State& state) static {
|
||||
std::mt19937_64 rng{123456};
|
||||
auto std_lower_bound = [](auto first, auto last, auto const& value) { return std::lower_bound(first, last, value); };
|
||||
auto std_lower_bound_pred = [](auto first, auto last, auto const& value) {
|
||||
return std::lower_bound(first, last, value, [](auto x, auto y) { return x < y; });
|
||||
};
|
||||
auto std_upper_bound = [](auto first, auto last, auto const& value) { return std::upper_bound(first, last, value); };
|
||||
auto std_upper_bound_pred = [](auto first, auto last, auto const& value) {
|
||||
return std::upper_bound(first, last, value, [](auto x, auto y) { return x < y; });
|
||||
};
|
||||
|
||||
using value_type = typename Container::value_type;
|
||||
// Benchmark {lower_bound,upper_bound} on a sorted sequence, looking up a random element that
|
||||
// is present in the sequence.
|
||||
auto bm = []<class Container>(std::string name, auto lookup) {
|
||||
benchmark::RegisterBenchmark(
|
||||
name,
|
||||
[lookup](auto& st) {
|
||||
using ValueType = typename Container::value_type;
|
||||
std::size_t const size = st.range(0);
|
||||
|
||||
std::vector<value_type> vec;
|
||||
for (int64_t i = 0; i != state.range(); ++i)
|
||||
vec.emplace_back(Generate<typename Container::value_type>::random());
|
||||
std::sort(vec.begin(), vec.end());
|
||||
// Random sorted data
|
||||
std::vector<ValueType> data(size);
|
||||
std::generate_n(data.begin(), size, &Generate<ValueType>::random);
|
||||
std::sort(data.begin(), data.end());
|
||||
|
||||
Container c(vec.begin(), vec.end());
|
||||
// Precompute a bunch of random keys.
|
||||
std::vector<ValueType> keys(data);
|
||||
std::shuffle(keys.begin(), keys.end(), getRandomEngine());
|
||||
|
||||
for (auto _ : state) {
|
||||
auto result = std::lower_bound(c.begin(), c.end(), vec[rng() % vec.size()]);
|
||||
benchmark::DoNotOptimize(result);
|
||||
benchmark::DoNotOptimize(vec);
|
||||
}
|
||||
};
|
||||
Container c(data.begin(), data.end());
|
||||
std::size_t pos = 0;
|
||||
for (auto _ : st) {
|
||||
benchmark::DoNotOptimize(c);
|
||||
auto result = lookup(c.begin(), c.end(), keys[pos]);
|
||||
benchmark::DoNotOptimize(result);
|
||||
if (++pos == keys.size())
|
||||
pos = 0;
|
||||
}
|
||||
})
|
||||
->Arg(8)
|
||||
->Arg(100)
|
||||
->Arg(8192);
|
||||
};
|
||||
|
||||
auto register_benchmark = [&]<class Container>(std::type_identity<Container>, std::string name) {
|
||||
benchmark::RegisterBenchmark(name, bm.template operator()<Container>)->Arg(8)->Arg(100)->Arg(8192);
|
||||
};
|
||||
bm.operator()<std::vector<int>>("std::lower_bound(std::vector<int>)", std_lower_bound);
|
||||
bm.operator()<std::deque<int>>("std::lower_bound(std::deque<int>)", std_lower_bound);
|
||||
bm.operator()<std::list<int>>("std::lower_bound(std::list<int>)", std_lower_bound);
|
||||
bm.operator()<std::forward_list<int>>("std::lower_bound(std::forward_list<int>)", std_lower_bound);
|
||||
bm.operator()<std::vector<int>>("std::lower_bound(std::vector<int>, pred)", std_lower_bound_pred);
|
||||
bm.operator()<std::deque<int>>("std::lower_bound(std::deque<int>, pred)", std_lower_bound_pred);
|
||||
bm.operator()<std::list<int>>("std::lower_bound(std::list<int>, pred)", std_lower_bound_pred);
|
||||
bm.operator()<std::forward_list<int>>("std::lower_bound(std::forward_list<int>, pred)", std_lower_bound_pred);
|
||||
|
||||
register_benchmark(std::type_identity<std::vector<int>>{}, "std::lower_bound(std::vector<int>)");
|
||||
register_benchmark(std::type_identity<std::deque<int>>{}, "std::lower_bound(std::deque<int>)");
|
||||
register_benchmark(std::type_identity<std::list<int>>{}, "std::lower_bound(std::list<int>)");
|
||||
register_benchmark(std::type_identity<std::forward_list<int>>{}, "std::lower_bound(std::forward_list<int>)");
|
||||
}
|
||||
{
|
||||
auto bm = []<class Container>(benchmark::State& state) static {
|
||||
std::mt19937_64 rng{123456};
|
||||
|
||||
using value_type = typename Container::value_type;
|
||||
|
||||
std::vector<value_type> vec;
|
||||
for (int64_t i = 0; i != state.range(); ++i)
|
||||
vec.emplace_back(Generate<typename Container::value_type>::random());
|
||||
std::sort(vec.begin(), vec.end());
|
||||
|
||||
Container c(vec.begin(), vec.end());
|
||||
|
||||
for (auto _ : state) {
|
||||
auto result = std::upper_bound(c.begin(), c.end(), vec[rng() % vec.size()]);
|
||||
benchmark::DoNotOptimize(result);
|
||||
benchmark::DoNotOptimize(vec);
|
||||
}
|
||||
};
|
||||
|
||||
auto register_benchmark = [&]<class Container>(std::type_identity<Container>, std::string name) {
|
||||
benchmark::RegisterBenchmark(name, bm.template operator()<Container>)->Arg(8)->Arg(100)->Arg(8192);
|
||||
};
|
||||
|
||||
register_benchmark(std::type_identity<std::vector<int>>{}, "std::upper_bound(std::vector<int>)");
|
||||
register_benchmark(std::type_identity<std::deque<int>>{}, "std::upper_bound(std::deque<int>)");
|
||||
register_benchmark(std::type_identity<std::list<int>>{}, "std::upper_bound(std::list<int>)");
|
||||
register_benchmark(std::type_identity<std::forward_list<int>>{}, "std::upper_bound(std::forward_list<int>)");
|
||||
}
|
||||
bm.operator()<std::vector<int>>("std::upper_bound(std::vector<int>)", std_upper_bound);
|
||||
bm.operator()<std::deque<int>>("std::upper_bound(std::deque<int>)", std_upper_bound);
|
||||
bm.operator()<std::list<int>>("std::upper_bound(std::list<int>)", std_upper_bound);
|
||||
bm.operator()<std::forward_list<int>>("std::upper_bound(std::forward_list<int>)", std_upper_bound);
|
||||
bm.operator()<std::vector<int>>("std::upper_bound(std::vector<int>, pred)", std_upper_bound_pred);
|
||||
bm.operator()<std::deque<int>>("std::upper_bound(std::deque<int>, pred)", std_upper_bound_pred);
|
||||
bm.operator()<std::list<int>>("std::upper_bound(std::list<int>, pred)", std_upper_bound_pred);
|
||||
bm.operator()<std::forward_list<int>>("std::upper_bound(std::forward_list<int>, pred)", std_upper_bound_pred);
|
||||
|
||||
benchmark::Initialize(&argc, argv);
|
||||
benchmark::RunSpecifiedBenchmarks();
|
||||
|
|
|
|||
Loading…
Reference in a new issue