[libcxx-commits] [libcxx] Libcxx nth element small ranges (PR #215698)

Adib Pathan via libcxx-commits libcxx-commits at lists.llvm.org
Wed Aug 12 21:46:52 PDT 2026


https://github.com/adxb-pxthxn updated https://github.com/llvm/llvm-project/pull/215698

>From 938cce62eab4769aa2d575596978037be1c3746c Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Wed, 12 Aug 2026 09:03:24 +0930
Subject: [PATCH 1/7] [libc++] Use sorting networks for nth_element on small
 ranges

nth_element previously used selection sort for ranges of 4-7 elements.
Dispatch ranges of 4 and 5 elements to the existing __sort4/__sort5
sorting networks instead, and perform that dispatch in the inlinable
wrapper behind __builtin_constant_p so that callers passing a range
whose size is a compile-time constant (e.g. a median-of-5 filter)
compile down to a branchless sorting network with no function call.

Fixes #214238
---
 libcxx/docs/ReleaseNotes/24.rst          |  4 ++
 libcxx/include/__algorithm/nth_element.h | 64 ++++++++++++++++++------
 libcxx/include/__algorithm/sort.h        |  8 +--
 3 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index e9a36ae94392f..1640bfcd3eb86 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -51,6 +51,10 @@ Implemented Papers
 Improvements and New Features
 -----------------------------
 
+- ``std::nth_element`` now uses sorting networks for ranges with at most 5 elements, which makes it significantly
+  faster on small ranges. In particular, when the size of the range is known at compile time (as in a median-of-5
+  filter), the algorithm now compiles down to a branchless sorting network.
+
 Deprecations and Removals
 -------------------------
 
diff --git a/libcxx/include/__algorithm/nth_element.h b/libcxx/include/__algorithm/nth_element.h
index da748d7255aba..53aca0b55b6a4 100644
--- a/libcxx/include/__algorithm/nth_element.h
+++ b/libcxx/include/__algorithm/nth_element.h
@@ -28,6 +28,43 @@ _LIBCPP_PUSH_MACROS
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
+// Handles ranges of at most 5 elements by sorting them entirely, which trivially satisfies the
+// nth_element postconditions. Sorting networks are the most efficient way to handle such small
+// ranges, and this function boils down to one of them when the size of the range is known at
+// compile time. Returns false if the range was too large to be handled.
+template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool
+__nth_element_small(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) {
+  using _Ops = _IterOps<_AlgPolicy>;
+  typedef typename iterator_traits<_RandomAccessIterator>::difference_type difference_type;
+  switch (__last - __first) {
+  case 0:
+  case 1:
+    return true;
+  case 2:
+    if (__comp(*--__last, *__first))
+      _Ops::iter_swap(__first, __last);
+    return true;
+  case 3:
+    std::__sort3<_AlgPolicy, _Compare>(__first, __first + difference_type(1), --__last, __comp);
+    return true;
+  case 4:
+    std::__sort4<_AlgPolicy, _Compare>(
+        __first, __first + difference_type(1), __first + difference_type(2), --__last, __comp);
+    return true;
+  case 5:
+    std::__sort5<_AlgPolicy, _Compare>(
+        __first,
+        __first + difference_type(1),
+        __first + difference_type(2),
+        __first + difference_type(3),
+        --__last,
+        __comp);
+    return true;
+  }
+  return false;
+}
+
 template <class _Compare, class _RandomAccessIterator>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __nth_element_find_guard(
     _RandomAccessIterator& __i, _RandomAccessIterator& __j, _RandomAccessIterator __m, _Compare __comp) {
@@ -55,21 +92,9 @@ __nth_element(
   while (true) {
     if (__nth == __last)
       return;
-    difference_type __len = __last - __first;
-    switch (__len) {
-    case 0:
-    case 1:
-      return;
-    case 2:
-      if (__comp(*--__last, *__first))
-        _Ops::iter_swap(__first, __last);
-      return;
-    case 3: {
-      _RandomAccessIterator __m = __first;
-      std::__sort3<_AlgPolicy, _Compare>(__first, ++__m, --__last, __comp);
+    if (std::__nth_element_small<_AlgPolicy, _Compare>(__first, __last, __comp))
       return;
-    }
-    }
+    difference_type __len = __last - __first;
     if (__len <= __limit) {
       std::__selection_sort<_AlgPolicy, _Compare>(__first, __last, __comp);
       return;
@@ -234,7 +259,16 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __nth_element_im
 
   std::__debug_randomize_range<_AlgPolicy>(__first, __last);
 
-  std::__nth_element<_AlgPolicy, __comp_ref_type<_Compare> >(__first, __nth, __last, __comp);
+  // When the size of the range is known at compile time (e.g. in a median-of-5 filter), this
+  // dispatch constant-folds to the appropriate sorting network. Otherwise, the check disappears
+  // entirely and we fall through to the general algorithm, which handles small ranges itself.
+  // Note that __builtin_constant_p is only resolved after inlining, so this catches callers that
+  // pass a constant size even though it is not a constant expression here.
+  bool __small_constant_size =
+      __builtin_constant_p(__last - __first) &&
+      std::__nth_element_small<_AlgPolicy, __comp_ref_type<_Compare> >(__first, __last, __comp);
+  if (!__small_constant_size)
+    std::__nth_element<_AlgPolicy, __comp_ref_type<_Compare> >(__first, __nth, __last, __comp);
 
   std::__debug_randomize_range<_AlgPolicy>(__first, __nth);
   if (__nth != __last) {
diff --git a/libcxx/include/__algorithm/sort.h b/libcxx/include/__algorithm/sort.h
index 0a936db78e5fd..4b450e7dc9194 100644
--- a/libcxx/include/__algorithm/sort.h
+++ b/libcxx/include/__algorithm/sort.h
@@ -140,7 +140,7 @@ template <class,
           class _Compare,
           class _RandomAccessIterator,
           __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
 __sort4(_RandomAccessIterator __x1,
         _RandomAccessIterator __x2,
         _RandomAccessIterator __x3,
@@ -157,7 +157,7 @@ template <class _AlgPolicy,
           class _Compare,
           class _RandomAccessIterator,
           __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
 __sort4(_RandomAccessIterator __x1,
         _RandomAccessIterator __x2,
         _RandomAccessIterator __x3,
@@ -182,7 +182,7 @@ template <class _AlgPolicy,
           class _Compare,
           class _RandomAccessIterator,
           __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
 __sort5(_RandomAccessIterator __x1,
         _RandomAccessIterator __x2,
         _RandomAccessIterator __x3,
@@ -201,7 +201,7 @@ template <class _AlgPolicy,
           class _Compare,
           class _RandomAccessIterator,
           __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
 __sort5(_RandomAccessIterator __x1,
         _RandomAccessIterator __x2,
         _RandomAccessIterator __x3,

>From 91642f38a620c458c6f4dcf428aaa9045a43456b Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Wed, 12 Aug 2026 09:10:50 +0930
Subject: [PATCH 2/7] [libc++] Add exhaustive tests and small-range benchmarks
 for nth_element

Add an exhaustive permutation/duplicate test covering the size-0..5
sorting-network paths, and extend the nth_element benchmark with
fixed-size median-of-3/5 windows that match the motivating use case.
---
 .../algorithms/sorting/nth_element.bench.cpp  |  49 +++++++
 .../nth_element.exhaustive.pass.cpp           | 123 ++++++++++++++++++
 2 files changed, 172 insertions(+)
 create mode 100644 libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp

diff --git a/libcxx/test/benchmarks/algorithms/sorting/nth_element.bench.cpp b/libcxx/test/benchmarks/algorithms/sorting/nth_element.bench.cpp
index 4c3e8115def30..d210e514a35fd 100644
--- a/libcxx/test/benchmarks/algorithms/sorting/nth_element.bench.cpp
+++ b/libcxx/test/benchmarks/algorithms/sorting/nth_element.bench.cpp
@@ -84,6 +84,55 @@ int main(int argc, char** argv) {
     register_bm(support::single_element_data<int>, "repeated");
   }
 
+  // Benchmark std::nth_element on many independent fixed-size windows. This models
+  // the median-of-3 / median-of-5 filter use case from #214238, where the window
+  // size is a compile-time constant and the specialized sorting-network path is
+  // expected to constant-fold away the general algorithm.
+  {
+    auto bm = []<std::size_t Window>(std::string name, auto generate_data) {
+      benchmark::RegisterBenchmark(
+          name,
+          [generate_data](auto& st) {
+            std::size_t const num_windows   = st.range(0);
+            constexpr std::size_t BatchSize = 32;
+            std::vector<int> flat           = generate_data(num_windows * Window);
+            std::array<std::vector<std::array<int, Window>>, BatchSize> batches;
+            for (std::size_t b = 0; b != BatchSize; ++b) {
+              batches[b].resize(num_windows);
+              for (std::size_t i = 0; i != num_windows; ++i) {
+                std::copy_n(flat.begin() + static_cast<std::ptrdiff_t>(i * Window), Window, batches[b][i].begin());
+              }
+            }
+
+            constexpr std::size_t Median = Window / 2;
+            while (st.KeepRunningBatch(BatchSize)) {
+              for (std::size_t b = 0; b != BatchSize; ++b) {
+                for (std::size_t i = 0; i != num_windows; ++i) {
+                  benchmark::DoNotOptimize(batches[b][i]);
+                  std::nth_element(batches[b][i].begin(), batches[b][i].begin() + Median, batches[b][i].end());
+                  benchmark::DoNotOptimize(batches[b][i]);
+                }
+              }
+
+              st.PauseTiming();
+              for (std::size_t b = 0; b != BatchSize; ++b) {
+                for (std::size_t i = 0; i != num_windows; ++i) {
+                  std::copy_n(flat.begin() + static_cast<std::ptrdiff_t>(i * Window), Window, batches[b][i].begin());
+                }
+              }
+              st.ResumeTiming();
+            }
+          })
+          ->Arg(1024)
+          ->Arg(8192);
+    };
+
+    bm.operator()<3>("std::nth_element(median-of-3 windows) (shuffled)", support::shuffled_data<int>);
+    bm.operator()<5>("std::nth_element(median-of-5 windows) (shuffled)", support::shuffled_data<int>);
+    bm.operator()<3>("std::nth_element(median-of-3 windows) (ascending)", support::ascending_sorted_data<int>);
+    bm.operator()<5>("std::nth_element(median-of-5 windows) (ascending)", support::ascending_sorted_data<int>);
+  }
+
   benchmark::Initialize(&argc, argv);
   benchmark::RunSpecifiedBenchmarks();
   benchmark::Shutdown();
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp
new file mode 100644
index 0000000000000..b747a18be8146
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp
@@ -0,0 +1,123 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// Exhaustively check std::nth_element on all permutations of small ranges.
+// This specifically covers the size-0..5 sorting-network specializations and the
+// size-6..7 selection-sort fallback.
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <numeric>
+#include <vector>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Iter, class Compare>
+void check_postcondition(Iter first, Iter nth, Iter last, Compare comp) {
+  if (nth == last)
+    return;
+  for (Iter i = first; i != nth; ++i)
+    assert(!comp(*nth, *i));
+  for (Iter i = nth; i != last; ++i)
+    assert(!comp(*i, *nth));
+}
+
+template <class Iter, class Compare>
+void test_permutations(std::vector<int> values, Compare comp) {
+  std::size_t const size = values.size();
+  std::vector<int> expected = values;
+  std::sort(expected.begin(), expected.end(), comp);
+
+  do {
+    for (std::size_t nth = 0; nth <= size; ++nth) {
+      std::vector<int> v = values;
+      std::nth_element(Iter(v.data()), Iter(v.data() + nth), Iter(v.data() + size), comp);
+      if (nth != size)
+        assert(v[nth] == expected[nth]);
+      assert(std::is_permutation(v.begin(), v.end(), values.begin()));
+      check_postcondition(Iter(v.data()), Iter(v.data() + nth), Iter(v.data() + size), comp);
+    }
+  } while (std::next_permutation(values.begin(), values.end()));
+}
+
+template <class Iter>
+void test_distinct() {
+  for (int size = 0; size <= 8; ++size) {
+    std::vector<int> values(static_cast<std::size_t>(size));
+    std::iota(values.begin(), values.end(), 0);
+    test_permutations<Iter>(values, std::less<int>());
+    test_permutations<Iter>(values, std::greater<int>());
+  }
+}
+
+// Exercise equal-element paths with every bit pattern of 0/1 for sizes 1..8.
+template <class Iter>
+void test_duplicates() {
+  for (int size = 1; size <= 8; ++size) {
+    for (int mask = 0; mask < (1 << size); ++mask) {
+      std::vector<int> values(static_cast<std::size_t>(size));
+      for (int i = 0; i < size; ++i)
+        values[static_cast<std::size_t>(i)] = (mask >> i) & 1;
+
+      std::vector<int> expected = values;
+      std::sort(expected.begin(), expected.end());
+      for (int nth = 0; nth < size; ++nth) {
+        std::vector<int> v = values;
+        std::nth_element(Iter(v.data()), Iter(v.data() + nth), Iter(v.data() + size));
+        assert(v[static_cast<std::size_t>(nth)] == expected[static_cast<std::size_t>(nth)]);
+        check_postcondition(Iter(v.data()), Iter(v.data() + nth), Iter(v.data() + size), std::less<int>());
+      }
+    }
+  }
+}
+
+#if TEST_STD_VER >= 20
+constexpr bool test_constexpr_small_ranges() {
+  // Sizes 3, 4 and 5 hit the specialized sorting-network dispatch.
+  {
+    int a[] = {2, 0, 1};
+    std::nth_element(a, a + 1, a + 3);
+    if (a[1] != 1)
+      return false;
+  }
+  {
+    int a[] = {3, 0, 2, 1};
+    std::nth_element(a, a + 2, a + 4);
+    if (a[2] != 2)
+      return false;
+  }
+  {
+    int a[] = {4, 2, 0, 3, 1};
+    std::nth_element(a, a + 2, a + 5);
+    if (a[2] != 2)
+      return false;
+  }
+  {
+    int a[] = {4, 2, 0, 3, 1};
+    std::nth_element(a, a + 2, a + 5, std::greater<>());
+    if (a[2] != 2)
+      return false;
+  }
+  return true;
+}
+static_assert(test_constexpr_small_ranges());
+#endif
+
+int main(int, char**) {
+  test_distinct<int*>();
+  test_distinct<random_access_iterator<int*> >();
+  test_duplicates<int*>();
+  test_duplicates<random_access_iterator<int*> >();
+
+  return 0;
+}

>From 72cc87f60541b78b5ff20fc5798ce5e53164f349 Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Wed, 12 Aug 2026 10:04:16 +0930
Subject: [PATCH 3/7] [libc++] Align nth_element exhaustive test for
 clang-format

---
 .../alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp
index b747a18be8146..1a471ac296346 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.nth.element/nth_element.exhaustive.pass.cpp
@@ -34,7 +34,7 @@ void check_postcondition(Iter first, Iter nth, Iter last, Compare comp) {
 
 template <class Iter, class Compare>
 void test_permutations(std::vector<int> values, Compare comp) {
-  std::size_t const size = values.size();
+  std::size_t const size    = values.size();
   std::vector<int> expected = values;
   std::sort(expected.begin(), expected.end(), comp);
 

>From 63a2a587735b588fd86cc3165127ef811f99c80f Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Thu, 13 Aug 2026 13:30:25 +0930
Subject: [PATCH 4/7] [libc++] Use a 6-comparison median-of-5 in nth_element

A full sort of 5 elements does more work than nth_element requires.
Select the median with 6 comparisons (Knuth, TAOCP 5.3.3) and partition
around it. Cheap arithmetic types still use a branchless 7-comparator
network.
---
 libcxx/include/__algorithm/nth_element.h | 86 +++++++++++++++++++++---
 1 file changed, 78 insertions(+), 8 deletions(-)

diff --git a/libcxx/include/__algorithm/nth_element.h b/libcxx/include/__algorithm/nth_element.h
index 53aca0b55b6a4..dca936d82d366 100644
--- a/libcxx/include/__algorithm/nth_element.h
+++ b/libcxx/include/__algorithm/nth_element.h
@@ -28,13 +28,79 @@ _LIBCPP_PUSH_MACROS
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-// Handles ranges of at most 5 elements by sorting them entirely, which trivially satisfies the
-// nth_element postconditions. Sorting networks are the most efficient way to handle such small
-// ranges, and this function boils down to one of them when the size of the range is known at
-// compile time. Returns false if the range was too large to be handled.
+// Selects the median of 5 elements and partitions the other 4 around it, so the nth_element
+// postconditions hold for nth == first + 2. Uses 6 comparisons, which is optimal
+// (Knuth, TAOCP 5.3.3).
+template <class _AlgPolicy,
+          class _Compare,
+          class _RandomAccessIterator,
+          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__nth_element_median5(_RandomAccessIterator __first, _Compare __comp) {
+  using _Ops = _IterOps<_AlgPolicy>;
+
+  _RandomAccessIterator __x1 = __first;
+  _RandomAccessIterator __x2 = __first + 1;
+  _RandomAccessIterator __x3 = __first + 2;
+  _RandomAccessIterator __x4 = __first + 3;
+  _RandomAccessIterator __x5 = __first + 4;
+
+  // Sort pairs (__x1, __x2) and (__x3, __x4).
+  if (__comp(*__x2, *__x1))
+    _Ops::iter_swap(__x1, __x2);
+  if (__comp(*__x4, *__x3))
+    _Ops::iter_swap(__x3, __x4);
+
+  // Order those pairs by their larger element: *__x1 <= *__x2 <= *__x4 and *__x3 <= *__x4.
+  // *__x4 has at least 3 elements <= it, so it cannot be the median.
+  if (__comp(*__x4, *__x2)) {
+    _Ops::iter_swap(__x1, __x3);
+    _Ops::iter_swap(__x2, __x4);
+  }
+
+  // Insert __x5 beside __x3.
+  if (__comp(*__x5, *__x3))
+    _Ops::iter_swap(__x3, __x5);
+
+  // Order pairs (__x1, __x2) and (__x3, __x5) by their larger element:
+  // *__x1 <= *__x2 <= *__x5 and *__x3 <= *__x5.
+  if (__comp(*__x5, *__x2)) {
+    _Ops::iter_swap(__x1, __x3);
+    _Ops::iter_swap(__x2, __x5);
+  }
+
+  // The median is the larger of __x2 and __x3.
+  if (__comp(*__x3, *__x2))
+    _Ops::iter_swap(__x2, __x3);
+}
+
+// Branchless 7-comparator selection network. One extra comparison vs the adaptive algorithm
+// above, but no control flow, which is faster for cheap arithmetic types.
+template <class,
+          class _Compare,
+          class _RandomAccessIterator,
+          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__nth_element_median5(_RandomAccessIterator __first, _Compare __comp) {
+  _RandomAccessIterator __x1 = __first;
+  _RandomAccessIterator __x2 = __first + 1;
+  _RandomAccessIterator __x3 = __first + 2;
+  _RandomAccessIterator __x4 = __first + 3;
+  _RandomAccessIterator __x5 = __first + 4;
+
+  std::__cond_swap<_Compare>(__x1, __x2, __comp);
+  std::__cond_swap<_Compare>(__x3, __x4, __comp);
+  std::__cond_swap<_Compare>(__x1, __x3, __comp);
+  std::__cond_swap<_Compare>(__x2, __x4, __comp);
+  std::__cond_swap<_Compare>(__x2, __x3, __comp);
+  std::__cond_swap<_Compare>(__x2, __x5, __comp);
+  std::__cond_swap<_Compare>(__x3, __x5, __comp);
+}
+
+// Handles ranges of at most 5 elements. Returns false if the range was too large to be handled.
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool
-__nth_element_small(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) {
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __nth_element_small(
+    _RandomAccessIterator __first, _RandomAccessIterator __nth, _RandomAccessIterator __last, _Compare __comp) {
   using _Ops = _IterOps<_AlgPolicy>;
   typedef typename iterator_traits<_RandomAccessIterator>::difference_type difference_type;
   switch (__last - __first) {
@@ -53,6 +119,10 @@ __nth_element_small(_RandomAccessIterator __first, _RandomAccessIterator __last,
         __first, __first + difference_type(1), __first + difference_type(2), --__last, __comp);
     return true;
   case 5:
+    if (__nth == __first + difference_type(2)) {
+      std::__nth_element_median5<_AlgPolicy, _Compare>(__first, __comp);
+      return true;
+    }
     std::__sort5<_AlgPolicy, _Compare>(
         __first,
         __first + difference_type(1),
@@ -92,7 +162,7 @@ __nth_element(
   while (true) {
     if (__nth == __last)
       return;
-    if (std::__nth_element_small<_AlgPolicy, _Compare>(__first, __last, __comp))
+    if (std::__nth_element_small<_AlgPolicy, _Compare>(__first, __nth, __last, __comp))
       return;
     difference_type __len = __last - __first;
     if (__len <= __limit) {
@@ -266,7 +336,7 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __nth_element_im
   // pass a constant size even though it is not a constant expression here.
   bool __small_constant_size =
       __builtin_constant_p(__last - __first) &&
-      std::__nth_element_small<_AlgPolicy, __comp_ref_type<_Compare> >(__first, __last, __comp);
+      std::__nth_element_small<_AlgPolicy, __comp_ref_type<_Compare> >(__first, __nth, __last, __comp);
   if (!__small_constant_size)
     std::__nth_element<_AlgPolicy, __comp_ref_type<_Compare> >(__first, __nth, __last, __comp);
 

>From 752753e1ab9e952fc6d24088274611679cac872d Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Thu, 13 Aug 2026 13:59:02 +0930
Subject: [PATCH 5/7] [libc++] Drop __builtin_constant_p explanation from
 nth_element

---
 libcxx/include/__algorithm/nth_element.h | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/libcxx/include/__algorithm/nth_element.h b/libcxx/include/__algorithm/nth_element.h
index dca936d82d366..9202f67703eba 100644
--- a/libcxx/include/__algorithm/nth_element.h
+++ b/libcxx/include/__algorithm/nth_element.h
@@ -330,10 +330,9 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __nth_element_im
   std::__debug_randomize_range<_AlgPolicy>(__first, __last);
 
   // When the size of the range is known at compile time (e.g. in a median-of-5 filter), this
-  // dispatch constant-folds to the appropriate sorting network. Otherwise, the check disappears
-  // entirely and we fall through to the general algorithm, which handles small ranges itself.
-  // Note that __builtin_constant_p is only resolved after inlining, so this catches callers that
-  // pass a constant size even though it is not a constant expression here.
+  // dispatch constant-folds to the appropriate small-range algorithm. Otherwise, the check
+  // disappears entirely and we fall through to the general algorithm, which handles small ranges
+  // itself.
   bool __small_constant_size =
       __builtin_constant_p(__last - __first) &&
       std::__nth_element_small<_AlgPolicy, __comp_ref_type<_Compare> >(__first, __nth, __last, __comp);

>From 62838cd275426ade899d0aae8d24fdc58e29a80c Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Thu, 13 Aug 2026 14:07:15 +0930
Subject: [PATCH 6/7] [libc++] Use a 3/4-comparison selector for 4-element
 nth_element A full sort of 4 elements takes 5 comparisons. Select the min or
 max with 3, or the 2nd smallest/largest with 4 (Knuth V_2(4)), and partition
 around it. Cheap arithmetic types still use the branchless sort4 network.

---
 libcxx/include/__algorithm/nth_element.h | 62 +++++++++++++++++++++++-
 1 file changed, 60 insertions(+), 2 deletions(-)

diff --git a/libcxx/include/__algorithm/nth_element.h b/libcxx/include/__algorithm/nth_element.h
index 9202f67703eba..6adb2b8381b3e 100644
--- a/libcxx/include/__algorithm/nth_element.h
+++ b/libcxx/include/__algorithm/nth_element.h
@@ -97,6 +97,65 @@ __nth_element_median5(_RandomAccessIterator __first, _Compare __comp) {
   std::__cond_swap<_Compare>(__x3, __x5, __comp);
 }
 
+// nth_element on 4 elements. 3 comparisons for the min or max, 4 for the two inner
+// positions (Knuth V_2(4)). A full sort would need 5.
+template <class _AlgPolicy,
+          class _Compare,
+          class _RandomAccessIterator,
+          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__nth_element4(_RandomAccessIterator __first, _RandomAccessIterator __nth, _Compare __comp) {
+  using _Ops = _IterOps<_AlgPolicy>;
+
+  _RandomAccessIterator __x1 = __first;
+  _RandomAccessIterator __x2 = __first + 1;
+  _RandomAccessIterator __x3 = __first + 2;
+  _RandomAccessIterator __x4 = __first + 3;
+
+  if (__comp(*__x2, *__x1))
+    _Ops::iter_swap(__x1, __x2);
+  if (__comp(*__x4, *__x3))
+    _Ops::iter_swap(__x3, __x4);
+
+  const typename iterator_traits<_RandomAccessIterator>::difference_type __k = __nth - __first;
+  if (__k == 0) {
+    if (__comp(*__x3, *__x1))
+      _Ops::iter_swap(__x1, __x3);
+    return;
+  }
+  if (__k == 3) {
+    if (__comp(*__x4, *__x2))
+      _Ops::iter_swap(__x2, __x4);
+    return;
+  }
+
+  if (__k == 1) {
+    // Order pairs by their smaller element so the min is at __x1.
+    if (__comp(*__x3, *__x1)) {
+      _Ops::iter_swap(__x1, __x3);
+      _Ops::iter_swap(__x2, __x4);
+    }
+  } else {
+    // Order pairs by their larger element so the max is at __x4.
+    if (__comp(*__x4, *__x2)) {
+      _Ops::iter_swap(__x1, __x3);
+      _Ops::iter_swap(__x2, __x4);
+    }
+  }
+  // 2nd smallest at __x2 (__k == 1), or 2nd largest at __x3 (__k == 2).
+  if (__comp(*__x3, *__x2))
+    _Ops::iter_swap(__x2, __x3);
+}
+
+template <class _AlgPolicy,
+          class _Compare,
+          class _RandomAccessIterator,
+          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__nth_element4(_RandomAccessIterator __first, _RandomAccessIterator, _Compare __comp) {
+  std::__sort4<_AlgPolicy, _Compare>(__first, __first + 1, __first + 2, __first + 3, __comp);
+}
+
 // Handles ranges of at most 5 elements. Returns false if the range was too large to be handled.
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __nth_element_small(
@@ -115,8 +174,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __nth_element_small(
     std::__sort3<_AlgPolicy, _Compare>(__first, __first + difference_type(1), --__last, __comp);
     return true;
   case 4:
-    std::__sort4<_AlgPolicy, _Compare>(
-        __first, __first + difference_type(1), __first + difference_type(2), --__last, __comp);
+    std::__nth_element4<_AlgPolicy, _Compare>(__first, __nth, __comp);
     return true;
   case 5:
     if (__nth == __first + difference_type(2)) {

>From 0351395632ef4f46db9598caa5b0f5e1e5217532 Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Thu, 13 Aug 2026 14:16:26 +0930
Subject: [PATCH 7/7] [libc++] Use a 2-comparison min/max for 3-element
 nth_element Keep sort3 for the median (it is a 3-sort) and for branchless
 arithmetic types. Selecting the min or max only needs 2 comparisons.

---
 libcxx/include/__algorithm/nth_element.h | 43 +++++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)

diff --git a/libcxx/include/__algorithm/nth_element.h b/libcxx/include/__algorithm/nth_element.h
index 6adb2b8381b3e..0275587a61031 100644
--- a/libcxx/include/__algorithm/nth_element.h
+++ b/libcxx/include/__algorithm/nth_element.h
@@ -156,6 +156,47 @@ __nth_element4(_RandomAccessIterator __first, _RandomAccessIterator, _Compare __
   std::__sort4<_AlgPolicy, _Compare>(__first, __first + 1, __first + 2, __first + 3, __comp);
 }
 
+// nth_element on 3 elements. Min or max takes 2 comparisons; the middle element is a
+// median-of-3, which is exactly a 3-sort.
+template <class _AlgPolicy,
+          class _Compare,
+          class _RandomAccessIterator,
+          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__nth_element3(_RandomAccessIterator __first, _RandomAccessIterator __nth, _Compare __comp) {
+  using _Ops = _IterOps<_AlgPolicy>;
+
+  _RandomAccessIterator __x1 = __first;
+  _RandomAccessIterator __x2 = __first + 1;
+  _RandomAccessIterator __x3 = __first + 2;
+
+  const typename iterator_traits<_RandomAccessIterator>::difference_type __k = __nth - __first;
+  if (__k == 0) {
+    if (__comp(*__x2, *__x1))
+      _Ops::iter_swap(__x1, __x2);
+    if (__comp(*__x3, *__x1))
+      _Ops::iter_swap(__x1, __x3);
+    return;
+  }
+  if (__k == 2) {
+    if (__comp(*__x2, *__x1))
+      _Ops::iter_swap(__x1, __x2);
+    if (__comp(*__x3, *__x2))
+      _Ops::iter_swap(__x2, __x3);
+    return;
+  }
+  std::__sort3<_AlgPolicy, _Compare>(__x1, __x2, __x3, __comp);
+}
+
+template <class _AlgPolicy,
+          class _Compare,
+          class _RandomAccessIterator,
+          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__nth_element3(_RandomAccessIterator __first, _RandomAccessIterator, _Compare __comp) {
+  std::__sort3<_AlgPolicy, _Compare>(__first, __first + 1, __first + 2, __comp);
+}
+
 // Handles ranges of at most 5 elements. Returns false if the range was too large to be handled.
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __nth_element_small(
@@ -171,7 +212,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __nth_element_small(
       _Ops::iter_swap(__first, __last);
     return true;
   case 3:
-    std::__sort3<_AlgPolicy, _Compare>(__first, __first + difference_type(1), --__last, __comp);
+    std::__nth_element3<_AlgPolicy, _Compare>(__first, __nth, __comp);
     return true;
   case 4:
     std::__nth_element4<_AlgPolicy, _Compare>(__first, __nth, __comp);



More information about the libcxx-commits mailing list