[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:
Louis Dionne 2026-07-15 13:22:25 -04:00 committed by GitHub
parent a16f3a0941
commit 0a933be62e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 309 additions and 55 deletions

View file

@ -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;
}

View 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;
}

View file

@ -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();