Skip to content

Conversation

@philnik777
Copy link
Contributor

@philnik777 philnik777 commented Dec 9, 2025

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

@github-actions
Copy link

github-actions bot commented Dec 9, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@ldionne ldionne marked this pull request as ready for review December 10, 2025 15:02
@ldionne ldionne requested a review from a team as a code owner December 10, 2025 15:02
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Dec 10, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 10, 2025

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

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

Full diff: https://github.com/llvm/llvm-project/pull/171389.diff

2 Files Affected:

  • (modified) libcxx/include/__algorithm/ranges_search_n.h (+2-2)
  • (modified) libcxx/include/__algorithm/search_n.h (+34-31)
diff --git a/libcxx/include/__algorithm/ranges_search_n.h b/libcxx/include/__algorithm/ranges_search_n.h
index 81b568c0965fd..746bfcc3d1a8f 100644
--- a/libcxx/include/__algorithm/ranges_search_n.h
+++ b/libcxx/include/__algorithm/ranges_search_n.h
@@ -54,8 +54,8 @@ struct __search_n {
       }
 
       if constexpr (random_access_iterator<_Iter1>) {
-        auto __ret = std::__search_n_random_access_impl<_RangeAlgPolicy>(
-            __first, __last, __count, __value, __pred, __proj, __size);
+        auto __ret =
+            std::__search_n_random_access_impl<_RangeAlgPolicy>(__first, __count, __value, __pred, __proj, __size);
         return {std::move(__ret.first), std::move(__ret.second)};
       }
     }
diff --git a/libcxx/include/__algorithm/search_n.h b/libcxx/include/__algorithm/search_n.h
index 38474e1b2379d..8d961489a46c2 100644
--- a/libcxx/include/__algorithm/search_n.h
+++ b/libcxx/include/__algorithm/search_n.h
@@ -11,7 +11,9 @@
 #define _LIBCPP___ALGORITHM_SEARCH_N_H
 
 #include <__algorithm/comp.h>
+#include <__algorithm/find.h>
 #include <__algorithm/iterator_operations.h>
+#include <__algorithm/unwrap_iter.h>
 #include <__config>
 #include <__functional/identity.h>
 #include <__iterator/advance.h>
@@ -68,43 +70,35 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 pair<_Iter, _Iter> __search_
   }
 }
 
-template <class _AlgPolicy, class _Pred, class _Iter, class _Sent, class _SizeT, class _Type, class _Proj, class _DiffT>
+template <class _AlgPolicy, class _Pred, class _Iter, class _SizeT, class _Type, class _Proj, class _DiffT>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 std::pair<_Iter, _Iter> __search_n_random_access_impl(
-    _Iter __first, _Sent __last, _SizeT __count, const _Type& __value, _Pred& __pred, _Proj& __proj, _DiffT __size1) {
-  using difference_type = typename iterator_traits<_Iter>::difference_type;
+    _Iter __first, _SizeT __count_in, const _Type& __value, _Pred& __pred, _Proj& __proj, _DiffT __size) {
+  auto __last  = __first + __size;
+  auto __count = static_cast<_DiffT>(__count_in);
+
   if (__count == 0)
     return std::make_pair(__first, __first);
-  if (__size1 < static_cast<_DiffT>(__count)) {
-    _IterOps<_AlgPolicy>::__advance_to(__first, __last);
-    return std::make_pair(__first, __first);
-  }
+  if (__size < __count)
+    return std::make_pair(__last, __last);
 
-  const auto __s = __first + __size1 - difference_type(__count - 1); // Start of pattern match can't go beyond here
+  _Iter __try_match_until = __last - __count;
+  _Iter __match_start     = __first;
+  _Iter __matched_until   = __first;
   while (true) {
-    // Find first element in sequence that matchs __value, with a mininum of loop checks
-    while (true) {
-      if (__first >= __s) { // return __last if no element matches __value
-        _IterOps<_AlgPolicy>::__advance_to(__first, __last);
-        return std::make_pair(__first, __first);
-      }
-      if (std::__invoke(__pred, std::__invoke(__proj, *__first), __value))
-        break;
-      ++__first;
-    }
-    // *__first matches __value_, now match elements after here
-    auto __m = __first;
-    _SizeT __c(0);
+    if (__match_start > __try_match_until)
+      return std::make_pair(__last, __last);
+
+    _Iter __check = __match_start + __count;
+
     while (true) {
-      if (++__c == __count) // If pattern exhausted, __first is the answer (works for 1 element pattern)
-        return std::make_pair(__first, __first + _DiffT(__count));
-      ++__m; // no need to check range on __m because __s guarantees we have enough source
+      if (__check == __matched_until)
+        return std::make_pair(__match_start, __match_start + __count);
 
-      // if there is a mismatch, restart with a new __first
-      if (!std::__invoke(__pred, std::__invoke(__proj, *__m), __value)) {
-        __first = __m;
-        ++__first;
+      if (!std::__invoke(__pred, std::__invoke(__proj, *--__check), __value)) {
+        __matched_until = __match_start + __count;
+        __match_start   = ++__check;
         break;
-      } // else there is a match, check next elements
+      }
     }
   }
 }
@@ -119,7 +113,7 @@ template <class _Iter,
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 pair<_Iter, _Iter>
 __search_n_impl(_Iter __first, _Sent __last, _DiffT __count, const _Type& __value, _Pred& __pred, _Proj& __proj) {
   return std::__search_n_random_access_impl<_ClassicAlgPolicy>(
-      __first, __last, __count, __value, __pred, __proj, __last - __first);
+      __first, __count, __value, __pred, __proj, __last - __first);
 }
 
 template <class _Iter1,
@@ -142,7 +136,16 @@ template <class _ForwardIterator, class _Size, class _Tp, class _BinaryPredicate
   static_assert(
       __is_callable<_BinaryPredicate&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
   auto __proj = __identity();
-  return std::__search_n_impl(__first, __last, std::__convert_to_integral(__count), __value, __pred, __proj).first;
+  return std::__rewrap_iter(
+      __first,
+      std::__search_n_impl(
+          std::__unwrap_iter(__first),
+          std::__unwrap_iter(__last),
+          std::__convert_to_integral(__count),
+          __value,
+          __pred,
+          __proj)
+          .first);
 }
 
 template <class _ForwardIterator, class _Size, class _Tp>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a description of the optimization to the commit message!

if (!std::__invoke(__pred, std::__invoke(__proj, *__m), __value)) {
__first = __m;
++__first;
if (!std::__invoke(__pred, std::__invoke(__proj, *--__check), __value)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest introducing __find_last to clean up these two nested while (true) loops:

auto __not_value = [&](auto const& __x) { return !std::__invoke(__pred, __x, __value); };
auto __res = std::__find_last(__match_start, __match_start + __count, __not_value, __proj);
if (__res == __match_start + __count) {
  // we have a match
} else {
  // no match :-(
}

We don't necessarily have to go all the way and refactor ranges::find_last to use it, but at least having a named algorithm for this is an improvement.

__is_callable<_BinaryPredicate&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
auto __proj = __identity();
return std::__search_n_impl(__first, __last, std::__convert_to_integral(__count), __value, __pred, __proj).first;
return std::__rewrap_iter(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like that is not necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a benchmark that intersperses near matches throughout the sequence. Something like https://github.com/llvm/llvm-project/blob/main/libcxx/test/benchmarks/algorithms/nonmodifying/search.bench.cpp#L85.

__first, __last, __count, __value, __pred, __proj, __last - __first);
__first, __count, __value, __pred, __proj, __last - __first);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not attached to this line: Let's add test coverage for having multiple subranges that partially match within a sequence. You can take inspiration from ranges::search_n for the corner cases that are covered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[libc++] Optimize std::search_n

3 participants