Nikolas Klauser 06c6a5020f
[libc++] Optimize search_n (#171389)
This changes the algorithm to more efficiently skip ranges which cannot
match the needle for random access iterators. Specifically, we now
search for a mismatching element from the back of the subrange we want
to check. When a mismatch occurs we can directly start one after the
mismatched element, since there cannot possibly be a matching subrange
starting between the start of the subrange we checked and the mismatched
element (since all elements have to be equal). The algorithm also
remembers the subrange which was already match as being equal and
doesn't try to compare it a second time, reducing the time spent in case
of a match.

```
Benchmark                                                       old             new    Difference    % Difference
---------------------------------------------------  --------------  --------------  ------------  --------------
rng::search_n(deque<int>)_(no_match)/1000                    458.33           14.22       -444.11         -96.90%
rng::search_n(deque<int>)_(no_match)/1024                    456.17           13.89       -442.28         -96.95%
rng::search_n(deque<int>)_(no_match)/1048576              453420.38           17.69    -453402.69        -100.00%
rng::search_n(deque<int>)_(no_match)/8192                   3566.08           17.60      -3548.49         -99.51%
rng::search_n(deque<int>,_pred)_(no_match)/1000              597.88           15.25       -582.63         -97.45%
rng::search_n(deque<int>,_pred)_(no_match)/1024              608.42           15.39       -593.03         -97.47%
rng::search_n(deque<int>,_pred)_(no_match)/1048576        594533.99           18.91    -594515.08        -100.00%
rng::search_n(deque<int>,_pred)_(no_match)/8192             4670.23           18.88      -4651.35         -99.60%
rng::search_n(list<int>)_(no_match)/1000                     733.72          730.22         -3.50          -0.48%
rng::search_n(list<int>)_(no_match)/1024                     759.93          753.10         -6.84          -0.90%
rng::search_n(list<int>)_(no_match)/1048576               833841.54       813483.75     -20357.79          -2.44%
rng::search_n(list<int>)_(no_match)/8192                    8352.18         8417.31         65.14           0.78%
rng::search_n(list<int>,_pred)_(no_match)/1000               776.79          789.72         12.93           1.66%
rng::search_n(list<int>,_pred)_(no_match)/1024               788.42          806.70         18.28           2.32%
rng::search_n(list<int>,_pred)_(no_match)/1048576         955536.40       982976.81      27440.41           2.87%
rng::search_n(list<int>,_pred)_(no_match)/8192              8874.02         8915.18         41.16           0.46%
rng::search_n(vector<int>)_(no_match)/1000                   212.69            3.79       -208.90         -98.22%
rng::search_n(vector<int>)_(no_match)/1024                   219.67            3.70       -215.96         -98.31%
rng::search_n(vector<int>)_(no_match)/1048576             209622.54            3.67    -209618.87        -100.00%
rng::search_n(vector<int>)_(no_match)/8192                  1643.80            3.83      -1639.98         -99.77%
rng::search_n(vector<int>,_pred)_(no_match)/1000             461.93            7.55       -454.38         -98.36%
rng::search_n(vector<int>,_pred)_(no_match)/1024             472.43            7.74       -464.69         -98.36%
rng::search_n(vector<int>,_pred)_(no_match)/1048576       546180.29            8.71    -546171.58        -100.00%
rng::search_n(vector<int>,_pred)_(no_match)/8192            3786.26            7.88      -3778.38         -99.79%
std::search_n(deque<int>)_(no_match)/1000                    455.53           14.19       -441.34         -96.88%
std::search_n(deque<int>)_(no_match)/1024                    459.79           13.98       -445.81         -96.96%
std::search_n(deque<int>)_(no_match)/1048576              449780.32           17.99    -449762.33        -100.00%
std::search_n(deque<int>)_(no_match)/8192                   3508.55           17.97      -3490.58         -99.49%
std::search_n(deque<int>,_pred)_(no_match)/1000              571.53           17.16       -554.37         -97.00%
std::search_n(deque<int>,_pred)_(no_match)/1024              584.43           17.09       -567.34         -97.08%
std::search_n(deque<int>,_pred)_(no_match)/1048576        581418.31           19.16    -581399.15        -100.00%
std::search_n(deque<int>,_pred)_(no_match)/8192             4661.97           19.36      -4642.61         -99.58%
std::search_n(list<int>)_(no_match)/1000                     722.45          710.39        -12.06          -1.67%
std::search_n(list<int>)_(no_match)/1024                     748.50          727.08        -21.42          -2.86%
std::search_n(list<int>)_(no_match)/1048576               821655.28       784520.12     -37135.16          -4.52%
std::search_n(list<int>)_(no_match)/8192                    7941.73         8002.05         60.32           0.76%
std::search_n(list<int>,_pred)_(no_match)/1000               766.59          786.31         19.72           2.57%
std::search_n(list<int>,_pred)_(no_match)/1024               785.92          804.43         18.51           2.35%
std::search_n(list<int>,_pred)_(no_match)/1048576         948252.76       969125.41      20872.65           2.20%
std::search_n(list<int>,_pred)_(no_match)/8192              8658.99         8825.71        166.72           1.93%
std::search_n(vector<int>)_(no_match)/1000                   210.36            3.47       -206.89         -98.35%
std::search_n(vector<int>)_(no_match)/1024                   217.60            4.13       -213.47         -98.10%
std::search_n(vector<int>)_(no_match)/1048576             209386.43            3.51    -209382.92        -100.00%
std::search_n(vector<int>)_(no_match)/8192                  1643.79            3.50      -1640.29         -99.79%
std::search_n(vector<int>,_pred)_(no_match)/1000             460.88            5.44       -455.45         -98.82%
std::search_n(vector<int>,_pred)_(no_match)/1024             475.36            5.43       -469.93         -98.86%
std::search_n(vector<int>,_pred)_(no_match)/1048576       682722.75            7.15    -682715.60        -100.00%
std::search_n(vector<int>,_pred)_(no_match)/8192            3779.95            5.43      -3774.52         -99.86%
Geomean                                                     4956.15           87.96      -4868.19         -98.23%
```

Fixes #129327
2026-01-08 11:26:08 +01:00

330 lines
11 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// <algorithm>
// UNSUPPORTED: c++03, c++11, c++14, c++17
// template<forward_iterator I, sentinel_for<I> S, class T,
// class Pred = ranges::equal_to, class Proj = identity>
// requires indirectly_comparable<I, const T*, Pred, Proj>
// constexpr subrange<I>
// ranges::search_n(I first, S last, iter_difference_t<I> count,
// const T& value, Pred pred = {}, Proj proj = {});
// template<forward_range R, class T, class Pred = ranges::equal_to,
// class Proj = identity>
// requires indirectly_comparable<iterator_t<R>, const T*, Pred, Proj>
// constexpr borrowed_subrange_t<R>
// ranges::search_n(R&& r, range_difference_t<R> count,
// const T& value, Pred pred = {}, Proj proj = {});
#include <algorithm>
#include <array>
#include <ranges>
#include "almost_satisfies_types.h"
#include "test_iterators.h"
template <class Iter1, class Sent1 = Iter1>
concept HasSearchNIt = requires (Iter1 first1, Sent1 last1) {
std::ranges::search_n(first1, last1, 0, 0);
};
static_assert(HasSearchNIt<int*>);
static_assert(!HasSearchNIt<ForwardIteratorNotDerivedFrom>);
static_assert(!HasSearchNIt<ForwardIteratorNotIncrementable>);
static_assert(!HasSearchNIt<int*, SentinelForNotSemiregular>);
static_assert(!HasSearchNIt<int*, SentinelForNotWeaklyEqualityComparableWith>);
static_assert(!HasSearchNIt<int**, int**>); // not indirectly comparable
template <class Range1, class Range2 = UncheckedRange<int*>>
concept HasSearchNR = requires (Range1 range) {
std::ranges::search_n(range, 0, 0);
};
static_assert(HasSearchNR<UncheckedRange<int*>>);
static_assert(!HasSearchNR<ForwardRangeNotDerivedFrom>);
static_assert(!HasSearchNR<ForwardIteratorNotIncrementable>);
static_assert(!HasSearchNR<ForwardRangeNotSentinelSemiregular>);
static_assert(!HasSearchNR<ForwardRangeNotSentinelEqualityComparableWith>);
static_assert(!HasSearchNR<UncheckedRange<int**>>); // not indirectly comparable
template <class Iter, class Sent = Iter>
constexpr void test_iterators() {
{ // simple test
{
int a[] = {1, 2, 3, 4, 5, 6};
std::same_as<std::ranges::subrange<Iter, Iter>> decltype(auto) ret =
std::ranges::search_n(Iter(a), Sent(Iter(a + 6)), 1, 3);
assert(base(ret.begin()) == a + 2);
assert(base(ret.end()) == a + 3);
}
{
int a[] = {1, 2, 3, 4, 5, 6};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 6)));
std::same_as<std::ranges::subrange<Iter, Iter>> decltype(auto) ret = std::ranges::search_n(range, 1, 3);
assert(base(ret.begin()) == a + 2);
assert(base(ret.end()) == a + 3);
}
}
{ // matching part begins at the front
{
int a[] = {7, 7, 3, 7, 3, 6};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 6)), 2, 7);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 2);
}
{
int a[] = {7, 7, 3, 7, 3, 6};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 6)));
auto ret = std::ranges::search_n(range, 2, 7);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 2);
}
}
{ // matching part ends at the back
{
int a[] = {9, 3, 6, 4, 4};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 5)), 2, 4);
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 5);
}
{
int a[] = {9, 3, 6, 4, 4};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 5)));
auto ret = std::ranges::search_n(range, 2, 4);
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 5);
}
}
{ // pattern does not match
{
int a[] = {9, 3, 6, 4, 8};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 5)), 1, 1);
assert(base(ret.begin()) == a + 5);
assert(base(ret.end()) == a + 5);
}
{
int a[] = {9, 3, 6, 4, 8};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 5)));
auto ret = std::ranges::search_n(range, 1, 1);
assert(base(ret.begin()) == a + 5);
assert(base(ret.end()) == a + 5);
}
}
{ // range and pattern are identical
{
int a[] = {1, 1, 1, 1};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 4)), 4, 1);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 4);
}
{
int a[] = {1, 1, 1, 1};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 4)));
auto ret = std::ranges::search_n(range, 4, 1);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 4);
}
}
{ // pattern is longer than range
{
int a[] = {3, 3, 3};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 3)), 4, 3);
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 3);
}
{
int a[] = {3, 3, 3};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 3)));
auto ret = std::ranges::search_n(range, 4, 3);
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 3);
}
}
{ // pattern has zero length
{
int a[] = {6, 7, 8};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 3)), 0, 7);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a);
}
{
int a[] = {6, 7, 8};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 3)));
auto ret = std::ranges::search_n(range, 0, 7);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a);
}
}
{ // range has zero length
{
std::array<int, 0> a = {};
auto ret = std::ranges::search_n(Iter(a.data()), Sent(Iter(a.data())), 1, 1);
assert(base(ret.begin()) == a.data());
assert(base(ret.end()) == a.data());
}
{
std::array<int, 0> a = {};
auto range = std::ranges::subrange(Iter(a.data()), Sent(Iter(a.data())));
auto ret = std::ranges::search_n(range, 1, 1);
assert(base(ret.begin()) == a.data());
assert(base(ret.end()) == a.data());
}
}
{ // check that the first match is returned
{ // Match is at the start
{
int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 8};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 9)), 2, 6);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 2);
}
{
int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 8};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 9)));
auto ret = std::ranges::search_n(range, 2, 6);
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 2);
}
}
{ // Match is in the middle
{
int a[] = {6, 8, 8, 6, 6, 8, 6, 6, 8};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 9)), 2, 6);
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 5);
}
{
int a[] = {6, 8, 8, 6, 6, 8, 6, 6, 8};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 9)));
auto ret = std::ranges::search_n(range, 2, 6);
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 5);
}
}
{ // Match is at the end
{
int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 6};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 9)), 3, 6);
assert(base(ret.begin()) == a + 6);
assert(base(ret.end()) == a + 9);
}
{
int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 6};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 9)));
auto ret = std::ranges::search_n(range, 3, 6);
assert(base(ret.begin()) == a + 6);
assert(base(ret.end()) == a + 9);
}
}
}
{ // check that the predicate is used
{
int a[] = {1, 4, 4, 3, 6, 1};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 6)),
3,
4,
[](int l, int r) { return l == r || l == 1; });
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 3);
}
{
int a[] = {1, 4, 4, 3, 6, 1};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 6)));
auto ret = std::ranges::search_n(range, 3, 4, [](int l, int r) { return l == r || l == 1; });
assert(base(ret.begin()) == a);
assert(base(ret.end()) == a + 3);
}
}
{ // check that the projections are used
{
int a[] = {1, 3, 1, 6, 5, 6};
auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 6)),
3,
6,
{},
[](int i) { return i % 2 == 0 ? i : i + 1; });
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 6);
}
{
int a[] = {1, 3, 1, 6, 5, 6};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 6)));
auto ret = std::ranges::search_n(range,
3,
6,
{},
[](int i) { return i % 2 == 0 ? i : i + 1; });
assert(base(ret.begin()) == a + 3);
assert(base(ret.end()) == a + 6);
}
}
}
constexpr bool test() {
test_iterators<forward_iterator<int*>>();
test_iterators<forward_iterator<int*>, sized_sentinel<forward_iterator<int*>>>();
test_iterators<bidirectional_iterator<int*>>();
test_iterators<bidirectional_iterator<int*>, sized_sentinel<bidirectional_iterator<int*>>>();
test_iterators<random_access_iterator<int*>>();
test_iterators<random_access_iterator<int*>, sized_sentinel<random_access_iterator<int*>>>();
test_iterators<contiguous_iterator<int*>>();
test_iterators<contiguous_iterator<int*>, sized_sentinel<contiguous_iterator<int*>>>();
test_iterators<int*>();
{ // check that std::invoke is used
struct S {
int i;
constexpr S identity() {
return *this;
}
constexpr bool compare(int o) {
return i == o;
}
};
{
S a[] = {{1}, {2}, {3}, {4}};
auto ret = std::ranges::search_n(a, a + 4, 1, 2, &S::compare, &S::identity);
assert(ret.begin() == a + 1);
assert(ret.end() == a + 2);
}
{
S a[] = {{1}, {2}, {3}, {4}};
auto ret = std::ranges::search_n(a, 1, 2, &S::compare, &S::identity);
assert(ret.begin() == a + 1);
assert(ret.end() == a + 2);
}
}
{ // check that std::ranges::dangling is returned
[[maybe_unused]] std::same_as<std::ranges::dangling> decltype(auto) ret =
std::ranges::search_n(std::array {1, 2, 3, 4}, 1, 0);
}
return true;
}
int main(int, char**) {
test();
static_assert(test());
return 0;
}