Skip to content

[libc++] Simplify the implementation of std::sort a bit #104902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 27, 2024

Conversation

philnik777
Copy link
Contributor

@philnik777 philnik777 commented Aug 20, 2024

This does a few things to canonicalize the library a bit. Specifically

  • use __desugars_to_v instead of the custom __is_simple_comparator
  • make __use_branchless_sort an inline variable
  • remove the _maybe_branchless versions of the __sortN functions and overload based on whether we can do branchless sorting instead.

@philnik777 philnik777 force-pushed the simplify_sort branch 2 times, most recently from 58a8d95 to d90d328 Compare August 20, 2024 08:48
@philnik777 philnik777 added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Aug 22, 2024
@llvmbot
Copy link
Member

llvmbot commented Aug 22, 2024

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

Changes

Patch is 21.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/104902.diff

8 Files Affected:

  • (modified) libcxx/include/__algorithm/comp.h (+3)
  • (modified) libcxx/include/__algorithm/ranges_minmax.h (+1-1)
  • (modified) libcxx/include/__algorithm/sort.h (+120-165)
  • (modified) libcxx/include/__functional/operations.h (+12)
  • (modified) libcxx/include/__functional/ranges_operations.h (+3)
  • (modified) libcxx/include/__type_traits/desugars_to.h (+10)
  • (modified) libcxx/include/__type_traits/is_trivially_copyable.h (+1-3)
  • (modified) libcxx/src/algorithm.cpp (+1-2)
diff --git a/libcxx/include/__algorithm/comp.h b/libcxx/include/__algorithm/comp.h
index 1f38f5d2d99b43..ab3c598418828a 100644
--- a/libcxx/include/__algorithm/comp.h
+++ b/libcxx/include/__algorithm/comp.h
@@ -42,6 +42,9 @@ struct __less<void, void> {
   }
 };
 
+template <class _Tp>
+inline const bool __desugars_to_v<__less_tag, __less<>, _Tp, _Tp> = true;
+
 template <class _Tp>
 inline const bool __desugars_to_v<__totally_ordered_less_tag, __less<>, _Tp, _Tp> = is_integral<_Tp>::value;
 
diff --git a/libcxx/include/__algorithm/ranges_minmax.h b/libcxx/include/__algorithm/ranges_minmax.h
index 9b8551d2213400..1b43b1e19cdec9 100644
--- a/libcxx/include/__algorithm/ranges_minmax.h
+++ b/libcxx/include/__algorithm/ranges_minmax.h
@@ -88,7 +88,7 @@ struct __minmax {
     // vectorize the code.
     if constexpr (contiguous_range<_Range> && is_integral_v<_ValueT> &&
                   __is_cheap_to_copy<_ValueT> & __is_identity<_Proj>::value &&
-                  __desugars_to_v<__totally_ordered_less_tag, _Comp, _ValueT, _ValueT>) {
+                  __desugars_to_v<__less_tag, _Comp, _ValueT, _ValueT>) {
       minmax_result<_ValueT> __result = {__r[0], __r[0]};
       for (auto __e : __r) {
         if (__e < __result.min)
diff --git a/libcxx/include/__algorithm/sort.h b/libcxx/include/__algorithm/sort.h
index 07b5814639e9e4..77ff1533c79492 100644
--- a/libcxx/include/__algorithm/sort.h
+++ b/libcxx/include/__algorithm/sort.h
@@ -27,9 +27,11 @@
 #include <__functional/ranges_operations.h>
 #include <__iterator/iterator_traits.h>
 #include <__type_traits/conditional.h>
+#include <__type_traits/desugars_to.h>
 #include <__type_traits/disjunction.h>
 #include <__type_traits/is_arithmetic.h>
 #include <__type_traits/is_constant_evaluated.h>
+#include <__type_traits/is_trivially_copyable.h>
 #include <__utility/move.h>
 #include <__utility/pair.h>
 #include <climits>
@@ -44,110 +46,11 @@ _LIBCPP_PUSH_MACROS
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-// stable, 2-3 compares, 0-2 swaps
-
-template <class _AlgPolicy, class _Compare, class _ForwardIterator>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 unsigned
-__sort3(_ForwardIterator __x, _ForwardIterator __y, _ForwardIterator __z, _Compare __c) {
-  using _Ops = _IterOps<_AlgPolicy>;
-
-  unsigned __r = 0;
-  if (!__c(*__y, *__x)) // if x <= y
-  {
-    if (!__c(*__z, *__y))      // if y <= z
-      return __r;              // x <= y && y <= z
-                               // x <= y && y > z
-    _Ops::iter_swap(__y, __z); // x <= z && y < z
-    __r = 1;
-    if (__c(*__y, *__x)) // if x > y
-    {
-      _Ops::iter_swap(__x, __y); // x < y && y <= z
-      __r = 2;
-    }
-    return __r; // x <= y && y < z
-  }
-  if (__c(*__z, *__y)) // x > y, if y > z
-  {
-    _Ops::iter_swap(__x, __z); // x < y && y < z
-    __r = 1;
-    return __r;
-  }
-  _Ops::iter_swap(__x, __y); // x > y && y <= z
-  __r = 1;                   // x < y && x <= z
-  if (__c(*__z, *__y))       // if y > z
-  {
-    _Ops::iter_swap(__y, __z); // x <= y && y < z
-    __r = 2;
-  }
-  return __r;
-} // x <= y && y <= z
-
-// stable, 3-6 compares, 0-5 swaps
-
-template <class _AlgPolicy, class _Compare, class _ForwardIterator>
-_LIBCPP_HIDE_FROM_ABI void
-__sort4(_ForwardIterator __x1, _ForwardIterator __x2, _ForwardIterator __x3, _ForwardIterator __x4, _Compare __c) {
-  using _Ops = _IterOps<_AlgPolicy>;
-  std::__sort3<_AlgPolicy, _Compare>(__x1, __x2, __x3, __c);
-  if (__c(*__x4, *__x3)) {
-    _Ops::iter_swap(__x3, __x4);
-    if (__c(*__x3, *__x2)) {
-      _Ops::iter_swap(__x2, __x3);
-      if (__c(*__x2, *__x1)) {
-        _Ops::iter_swap(__x1, __x2);
-      }
-    }
-  }
-}
-
-// stable, 4-10 compares, 0-9 swaps
-
-template <class _AlgPolicy, class _Comp, class _ForwardIterator>
-_LIBCPP_HIDE_FROM_ABI void
-__sort5(_ForwardIterator __x1,
-        _ForwardIterator __x2,
-        _ForwardIterator __x3,
-        _ForwardIterator __x4,
-        _ForwardIterator __x5,
-        _Comp __comp) {
-  using _Ops = _IterOps<_AlgPolicy>;
-
-  std::__sort4<_AlgPolicy, _Comp>(__x1, __x2, __x3, __x4, __comp);
-  if (__comp(*__x5, *__x4)) {
-    _Ops::iter_swap(__x4, __x5);
-    if (__comp(*__x4, *__x3)) {
-      _Ops::iter_swap(__x3, __x4);
-      if (__comp(*__x3, *__x2)) {
-        _Ops::iter_swap(__x2, __x3);
-        if (__comp(*__x2, *__x1)) {
-          _Ops::iter_swap(__x1, __x2);
-        }
-      }
-    }
-  }
-}
-
-// The comparator being simple is a prerequisite for using the branchless optimization.
-template <class _Tp>
-struct __is_simple_comparator : false_type {};
-template <>
-struct __is_simple_comparator<__less<>&> : true_type {};
-template <class _Tp>
-struct __is_simple_comparator<less<_Tp>&> : true_type {};
-template <class _Tp>
-struct __is_simple_comparator<greater<_Tp>&> : true_type {};
-#if _LIBCPP_STD_VER >= 20
-template <>
-struct __is_simple_comparator<ranges::less&> : true_type {};
-template <>
-struct __is_simple_comparator<ranges::greater&> : true_type {};
-#endif
-
 template <class _Compare, class _Iter, class _Tp = typename iterator_traits<_Iter>::value_type>
-using __use_branchless_sort =
-    integral_constant<bool,
-                      __libcpp_is_contiguous_iterator<_Iter>::value && sizeof(_Tp) <= sizeof(void*) &&
-                          is_arithmetic<_Tp>::value && __is_simple_comparator<_Compare>::value>;
+inline const bool __use_branchless_sort =
+    __libcpp_is_contiguous_iterator<_Iter>::value && __is_cheap_to_copy<_Tp> && is_arithmetic<_Tp>::value &&
+    (__desugars_to_v<__less_tag, __remove_cvref_t<_Compare>, _Tp, _Tp> ||
+     __desugars_to_v<__greater_tag, __remove_cvref_t<_Compare>, _Tp, _Tp>);
 
 namespace __detail {
 
@@ -158,59 +61,88 @@ enum { __block_size = sizeof(uint64_t) * 8 };
 
 // Ensures that __c(*__x, *__y) is true by swapping *__x and *__y if necessary.
 template <class _Compare, class _RandomAccessIterator>
-inline _LIBCPP_HIDE_FROM_ABI void __cond_swap(_RandomAccessIterator __x, _RandomAccessIterator __y, _Compare __c) {
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool
+__cond_swap(_RandomAccessIterator __x, _RandomAccessIterator __y, _Compare __c) {
   // Note: this function behaves correctly even with proxy iterators (because it relies on `value_type`).
   using value_type = typename iterator_traits<_RandomAccessIterator>::value_type;
   bool __r         = __c(*__x, *__y);
   value_type __tmp = __r ? *__x : *__y;
   *__y             = __r ? *__y : *__x;
   *__x             = __tmp;
+  return !__r;
 }
 
 // Ensures that *__x, *__y and *__z are ordered according to the comparator __c,
 // under the assumption that *__y and *__z are already ordered.
 template <class _Compare, class _RandomAccessIterator>
-inline _LIBCPP_HIDE_FROM_ABI void
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool
 __partially_sorted_swap(_RandomAccessIterator __x, _RandomAccessIterator __y, _RandomAccessIterator __z, _Compare __c) {
   // Note: this function behaves correctly even with proxy iterators (because it relies on `value_type`).
   using value_type = typename iterator_traits<_RandomAccessIterator>::value_type;
-  bool __r         = __c(*__z, *__x);
-  value_type __tmp = __r ? *__z : *__x;
-  *__z             = __r ? *__x : *__z;
-  __r              = __c(__tmp, *__y);
-  *__x             = __r ? *__x : *__y;
-  *__y             = __r ? *__y : __tmp;
+  bool __r1        = __c(*__z, *__x);
+  value_type __tmp = __r1 ? *__z : *__x;
+  *__z             = __r1 ? *__x : *__z;
+  bool __r2        = __c(__tmp, *__y);
+  *__x             = __r2 ? *__x : *__y;
+  *__y             = __r2 ? *__y : __tmp;
+  return !__r1 || !__r2;
 }
 
+// stable, 2-3 compares, 0-2 swaps
+
 template <class,
           class _Compare,
           class _RandomAccessIterator,
-          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>::value, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void __sort3_maybe_branchless(
-    _RandomAccessIterator __x1, _RandomAccessIterator __x2, _RandomAccessIterator __x3, _Compare __c) {
-  std::__cond_swap<_Compare>(__x2, __x3, __c);
-  std::__partially_sorted_swap<_Compare>(__x1, __x2, __x3, __c);
+          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool
+__sort3(_RandomAccessIterator __x1, _RandomAccessIterator __x2, _RandomAccessIterator __x3, _Compare __c) {
+  bool __swapped1 = std::__cond_swap<_Compare>(__x2, __x3, __c);
+  bool __swapped2 = std::__partially_sorted_swap<_Compare>(__x1, __x2, __x3, __c);
+  return __swapped1 || __swapped2;
 }
 
 template <class _AlgPolicy,
           class _Compare,
           class _RandomAccessIterator,
-          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>::value, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void __sort3_maybe_branchless(
-    _RandomAccessIterator __x1, _RandomAccessIterator __x2, _RandomAccessIterator __x3, _Compare __c) {
-  std::__sort3<_AlgPolicy, _Compare>(__x1, __x2, __x3, __c);
-}
+          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool
+__sort3(_RandomAccessIterator __x, _RandomAccessIterator __y, _RandomAccessIterator __z, _Compare __c) {
+  using _Ops = _IterOps<_AlgPolicy>;
+
+  if (!__c(*__y, *__x)) // if x <= y
+  {
+    if (!__c(*__z, *__y))        // if y <= z
+      return false;              // x <= y && y <= z
+                                 // x <= y && y > z
+    _Ops::iter_swap(__y, __z);   // x <= z && y < z
+    if (__c(*__y, *__x))         // if x > y
+      _Ops::iter_swap(__x, __y); // x < y && y <= z
+    return true;                 // x <= y && y < z
+  }
+  if (__c(*__z, *__y)) // x > y, if y > z
+  {
+    _Ops::iter_swap(__x, __z); // x < y && y < z
+    return true;
+  }
+  _Ops::iter_swap(__x, __y); // x > y && y <= z
+  // x < y && x <= z
+  if (__c(*__z, *__y))         // if y > z
+    _Ops::iter_swap(__y, __z); // x <= y && y < z
+  return true;
+} // x <= y && y <= z
+
+// stable, 3-6 compares, 0-5 swaps
 
 template <class,
           class _Compare,
           class _RandomAccessIterator,
-          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>::value, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void __sort4_maybe_branchless(
-    _RandomAccessIterator __x1,
-    _RandomAccessIterator __x2,
-    _RandomAccessIterator __x3,
-    _RandomAccessIterator __x4,
-    _Compare __c) {
+          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+inline _LIBCPP_HIDE_FROM_ABI void
+__sort4(_RandomAccessIterator __x1,
+        _RandomAccessIterator __x2,
+        _RandomAccessIterator __x3,
+        _RandomAccessIterator __x4,
+        _Compare __c) {
   std::__cond_swap<_Compare>(__x1, __x3, __c);
   std::__cond_swap<_Compare>(__x2, __x4, __c);
   std::__cond_swap<_Compare>(__x1, __x2, __c);
@@ -221,27 +153,39 @@ inline _LIBCPP_HIDE_FROM_ABI void __sort4_maybe_branchless(
 template <class _AlgPolicy,
           class _Compare,
           class _RandomAccessIterator,
-          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>::value, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void __sort4_maybe_branchless(
-    _RandomAccessIterator __x1,
-    _RandomAccessIterator __x2,
-    _RandomAccessIterator __x3,
-    _RandomAccessIterator __x4,
-    _Compare __c) {
-  std::__sort4<_AlgPolicy, _Compare>(__x1, __x2, __x3, __x4, __c);
+          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+inline _LIBCPP_HIDE_FROM_ABI void
+__sort4(_RandomAccessIterator __x1,
+        _RandomAccessIterator __x2,
+        _RandomAccessIterator __x3,
+        _RandomAccessIterator __x4,
+        _Compare __c) {
+  using _Ops = _IterOps<_AlgPolicy>;
+  std::__sort3<_AlgPolicy, _Compare>(__x1, __x2, __x3, __c);
+  if (__c(*__x4, *__x3)) {
+    _Ops::iter_swap(__x3, __x4);
+    if (__c(*__x3, *__x2)) {
+      _Ops::iter_swap(__x2, __x3);
+      if (__c(*__x2, *__x1)) {
+        _Ops::iter_swap(__x1, __x2);
+      }
+    }
+  }
 }
 
+// stable, 4-10 compares, 0-9 swaps
+
 template <class _AlgPolicy,
           class _Compare,
           class _RandomAccessIterator,
-          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>::value, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void __sort5_maybe_branchless(
-    _RandomAccessIterator __x1,
-    _RandomAccessIterator __x2,
-    _RandomAccessIterator __x3,
-    _RandomAccessIterator __x4,
-    _RandomAccessIterator __x5,
-    _Compare __c) {
+          __enable_if_t<__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+inline _LIBCPP_HIDE_FROM_ABI void
+__sort5(_RandomAccessIterator __x1,
+        _RandomAccessIterator __x2,
+        _RandomAccessIterator __x3,
+        _RandomAccessIterator __x4,
+        _RandomAccessIterator __x5,
+        _Compare __c) {
   std::__cond_swap<_Compare>(__x1, __x2, __c);
   std::__cond_swap<_Compare>(__x4, __x5, __c);
   std::__partially_sorted_swap<_Compare>(__x3, __x4, __x5, __c);
@@ -253,16 +197,29 @@ inline _LIBCPP_HIDE_FROM_ABI void __sort5_maybe_branchless(
 template <class _AlgPolicy,
           class _Compare,
           class _RandomAccessIterator,
-          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>::value, int> = 0>
-inline _LIBCPP_HIDE_FROM_ABI void __sort5_maybe_branchless(
-    _RandomAccessIterator __x1,
-    _RandomAccessIterator __x2,
-    _RandomAccessIterator __x3,
-    _RandomAccessIterator __x4,
-    _RandomAccessIterator __x5,
-    _Compare __c) {
-  std::__sort5<_AlgPolicy, _Compare, _RandomAccessIterator>(
-      std::move(__x1), std::move(__x2), std::move(__x3), std::move(__x4), std::move(__x5), __c);
+          __enable_if_t<!__use_branchless_sort<_Compare, _RandomAccessIterator>, int> = 0>
+inline _LIBCPP_HIDE_FROM_ABI void
+__sort5(_RandomAccessIterator __x1,
+        _RandomAccessIterator __x2,
+        _RandomAccessIterator __x3,
+        _RandomAccessIterator __x4,
+        _RandomAccessIterator __x5,
+        _Compare __comp) {
+  using _Ops = _IterOps<_AlgPolicy>;
+
+  std::__sort4<_AlgPolicy, _Compare>(__x1, __x2, __x3, __x4, __comp);
+  if (__comp(*__x5, *__x4)) {
+    _Ops::iter_swap(__x4, __x5);
+    if (__comp(*__x4, *__x3)) {
+      _Ops::iter_swap(__x3, __x4);
+      if (__comp(*__x3, *__x2)) {
+        _Ops::iter_swap(__x2, __x3);
+        if (__comp(*__x2, *__x1)) {
+          _Ops::iter_swap(__x1, __x2);
+        }
+      }
+    }
+  }
 }
 
 // Assumes size > 0
@@ -352,14 +309,14 @@ __insertion_sort_incomplete(_RandomAccessIterator __first, _RandomAccessIterator
       _Ops::iter_swap(__first, __last);
     return true;
   case 3:
-    std::__sort3_maybe_branchless<_AlgPolicy, _Comp>(__first, __first + difference_type(1), --__last, __comp);
+    std::__sort3<_AlgPolicy, _Comp>(__first, __first + difference_type(1), --__last, __comp);
     return true;
   case 4:
-    std::__sort4_maybe_branchless<_AlgPolicy, _Comp>(
+    std::__sort4<_AlgPolicy, _Comp>(
         __first, __first + difference_type(1), __first + difference_type(2), --__last, __comp);
     return true;
   case 5:
-    std::__sort5_maybe_branchless<_AlgPolicy, _Comp>(
+    std::__sort5<_AlgPolicy, _Comp>(
         __first,
         __first + difference_type(1),
         __first + difference_type(2),
@@ -370,7 +327,7 @@ __insertion_sort_incomplete(_RandomAccessIterator __first, _RandomAccessIterator
   }
   typedef typename iterator_traits<_RandomAccessIterator>::value_type value_type;
   _RandomAccessIterator __j = __first + difference_type(2);
-  std::__sort3_maybe_branchless<_AlgPolicy, _Comp>(__first, __first + difference_type(1), __j, __comp);
+  std::__sort3<_AlgPolicy, _Comp>(__first, __first + difference_type(1), __j, __comp);
   const unsigned __limit = 8;
   unsigned __count       = 0;
   for (_RandomAccessIterator __i = __j + difference_type(1); __i != __last; ++__i) {
@@ -777,14 +734,14 @@ void __introsort(_RandomAccessIterator __first,
         _Ops::iter_swap(__first, __last);
       return;
     case 3:
-      std::__sort3_maybe_branchless<_AlgPolicy, _Compare>(__first, __first + difference_type(1), --__last, __comp);
+      std::__sort3<_AlgPolicy, _Compare>(__first, __first + difference_type(1), --__last, __comp);
       return;
     case 4:
-      std::__sort4_maybe_branchless<_AlgPolicy, _Compare>(
+      std::__sort4<_AlgPolicy, _Compare>(
           __first, __first + difference_type(1), __first + difference_type(2), --__last, __comp);
       return;
     case 5:
-      std::__sort5_maybe_branchless<_AlgPolicy, _Compare>(
+      std::__sort5<_AlgPolicy, _Compare>(
           __first,
           __first + difference_type(1),
           __first + difference_type(2),
@@ -925,10 +882,8 @@ __sort_dispatch(_RandomAccessIterator __first, _RandomAccessIterator __last, _Co
   // Only use bitset partitioning for arithmetic types.  We should also check
   // that the default comparator is in use so that we are sure that there are no
   // branches in the comparator.
-  std::__introsort<_AlgPolicy,
-                   _Comp&,
-                   _RandomAccessIterator,
-                   __use_branchless_sort<_Comp, _RandomAccessIterator>::value>(__first, __last, __comp, __depth_limit);
+  std::__introsort<_AlgPolicy, _Comp&, _RandomAccessIterator, __use_branchless_sort<_Comp, _RandomAccessIterator> >(
+      __first, __last, __comp, __depth_limit);
 }
 
 template <class _Type, class... _Options>
diff --git a/libcxx/include/__functional/operations.h b/libcxx/include/__functional/operations.h
index 6022bd679ed3e3..67d9da289aead3 100644
--- a/libcxx/include/__functional/operations.h
+++ b/libcxx/include/__functional/operations.h
@@ -362,6 +362,9 @@ struct _LIBCPP_TEMPLATE_VIS less : __binary_function<_Tp, _Tp, bool> {
 };
 _LIBCPP_CTAD_SUPPORTED_FOR_TYPE(less);
 
+template <class _Tp>
+inline const bool __desugars_to_v<__less_tag, less<_Tp>, _Tp, _Tp> = true;
+
 template <class _Tp>
 inline const bool __desugars_to_v<__totally_ordered_less_tag, less<_Tp>, _Tp, _Tp> = is_integral<_Tp>::value;
 
@@ -377,6 +380,9 @@ struct _LIBCPP_TEMPLATE_VIS less<void> {
   typedef void is_transparent;
 };
 
+template <class _Tp, class _Up>
+inline const bool __desugars_to_v<__less_tag, less<>, _Tp, _Up> = true;
+
 template <class _Tp>
 inline const bool __desugars_to_v<__totally_ordered_less_tag, less<>, _Tp, _Tp> = is_integral<_Tp>::value;
 #endif
@@ -446,6 +452,9 @@ struct _LIBCPP_TEMPLATE_VIS greater : __binary_function<_Tp, _Tp, bool> {
 };
 _LIBCPP_CTAD_SUPPORTED_FOR_TYPE(greater);
 
+template <class _Tp>
+inline const bool __desugars_to_v<__greater_tag, greater<_Tp>, _Tp, _Tp> = true;
+
 #if _LIBCPP_STD_VER >= 14
 template <>
 struct _LIBCPP_TEMPLATE_VIS greater<void> {
@@ -457,6 +466,9 @@ struct _LIBCPP_TEMPLATE_VIS greater<void> {
   }
   typedef void is_transparent;
 };
+
+template <class _Tp, class _Up>
+inline const bool __desugars_to_v<__greater_tag, greater<>, _Tp, _Up> = true;
 #endif
 
 // Logical operations
diff --git a/libcxx/include/__functional/ranges_operations.h b/libcxx/include/__functional/ranges_operations.h
index f023d765a6c8ab..c8210193257c03 100644
--- a/libcxx/include/__functional/ranges_operations.h
+++ b/libcxx/include/__functional/ranges_operations.h
@@ -102,6 +102,9 @@ inline const bool __desugars_to_v<__equal_tag, ranges::equal_to, _Tp, _Up> = tru
 template <class _Tp, class _Up>
 inline const bool __desugars_to_v<__totally_ordered_less_tag, ranges::less, _Tp, _Up> = true;
 
+template <class _Tp, class _Up>
+inline const bool __desugars_to_v<__greater_tag, ranges::greater, _Tp, _Up> = true;
+
 #endif // _LIBCPP_STD_VER >= 20
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__type_traits/desugars_to.h b/libcxx/include/__type_traits/desugars_to.h
index b0ce7c414e5d77..d74830de9e0d7c 100644
--- a/libcxx/include/__type_traits/desugars_to.h
+++ b/libcxx/include/__type_traits/desugars_to.h
@@ -25,6 +25,12 @@ struct __equal_tag {};
 // syntactically, the operation is equivalent to calling `a + b`
 struct __plus_tag {};
 
+// syntactically, the operation is equivalent to calling `a < b`
+struct __less_tag {};
+
+// syntactically, the operation is equivalent to calling `a > b`
+struct __greater_tag {};
+
 // syntactically, the operation is equivalent to calling `a < b`, and these expressions
 // have to be true for any `a` and `b`:
 // - `...
[truncated]

@ldionne ldionne marked this pull request as ready for review August 22, 2024 15:51
@ldionne ldionne requested a review from a team as a code owner August 22, 2024 15:51
Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

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

LGTM after live review, but please write an explanation of the patch in the PR description since it may not be obvious. This is basically a NFC refactoring without meaningful functionality change, but it's not immediately obvious.

@philnik777 philnik777 merged commit d4ffccf into llvm:main Aug 27, 2024
54 of 56 checks passed
@philnik777 philnik777 deleted the simplify_sort branch August 27, 2024 14:54
@cjdb
Copy link
Contributor

cjdb commented Sep 16, 2024

We've observed ~20% regressions when sorting small collections (e.g. 300 elements or fewer). A reproducer from the team that made this observation should hopefully arrive in the near future.

Should we roll this back and re-apply once the regression is addressed, or would it be better to prioritise a forward fix?

@philnik777
Copy link
Contributor Author

@cjdb Since this isn't a correctness issue I'd rather fix forward. I've had someone claim a performance regression and them revert just for me having to press for information one too many times. If there is a fundamental problem with this code (which I doubt) we can still revert (parts of) it.

@joanahalili
Copy link

joanahalili commented Sep 18, 2024

Regarding the performance regression, here is a rough example of what we are seeing internally. https://godbolt.org/z/6zqEa4xEP
The godbolt link compares the x86-64 clang 18.1 with trunk, but we have reproduced this internally and can see the regression for smaller values caused by this commit.

@alexfh
Copy link
Contributor

alexfh commented Sep 18, 2024

In addition to @joanahalili's comment: the regression varies from 5% to 25% depending on the platform. And there's also a much more significant improvement for larger vector sizes (5000+) - around 1.3-2x speedup, but the code is used with smaller vector sizes in prod, which makes the improvement irrelevant to us.

@ldionne
Copy link
Member

ldionne commented Sep 19, 2024

@philnik777 I would suggest reverting this and then splitting it into a few sub-commits. There were a few orthogonal changes in this patch that we could split out to isolate what's causing the regression.

@eaeltsin
Copy link
Contributor

If there is a consensus to revert this, can we please do it ASAP?

Our internal releases are blocked because of unclear performance implications.

ldionne added a commit that referenced this pull request Sep 20, 2024
)"

This reverts commit d4ffccf, which
caused a performance regression that needs to be investigated further.
@ldionne
Copy link
Member

ldionne commented Sep 20, 2024

I reverted for now. I'll sync up with @philnik777 .

@eaeltsin
Copy link
Contributor

Thank you!

@danlark1
Copy link
Contributor

In addition to @joanahalili's comment: the regression varies from 5% to 25% depending on the platform. And there's also a much more significant improvement for larger vector sizes (5000+) - around 1.3-2x speedup, but the code is used with smaller vector sizes in prod, which makes the improvement irrelevant to us.

To provide more context. The situation is more nuanced. I am also from Google and was one of the people who developed new std::sort implementation back in 2022/2023.

The patch is correct. It fixed a problem which was introduced (oh my) on Feb 1, 2023 here.

Note that __use_branchless_sort started to be used with a template arg _Comp, not _Comp& but __use_branchless_sort uses __is_simple_comparator which accepts only references

template <class _Tp>
struct __is_simple_comparator : false_type {};
template <>
struct __is_simple_comparator<__less<>&> : true_type {};
template <class _Tp>
struct __is_simple_comparator<less<_Tp>&> : true_type {};
template <class _Tp>
struct __is_simple_comparator<greater<_Tp>&> : true_type {};
#if _LIBCPP_STD_VER >= 20
template <>
struct __is_simple_comparator<ranges::less&> : true_type {};
template <>
struct __is_simple_comparator<ranges::greater&> : true_type {};
#endif

See godbolt for __use_branchless_sort dispatch https://gcc.godbolt.org/z/8e9TffYb6

Disabling branch free version of bitset partitioning is not the desired behavior (which was from Feb 1, 2023). Branch free version was developed when we changed std::sort implementation and that was the whole point of doing so. This patch fixes it but regressions of 200-300 elements are real as floats and doubles are slightly harder to move branch free. I checked the same benchmarks on integers and they are fine for <100 elements.

Let's move forward with this patch. We checked and actually biggest clients of double and float sorting are kind of big ones. If everybody here begins to be concerned with <200 elements sorts for floats, we can disable is_arithmetic

@ldionne
Copy link
Member

ldionne commented Oct 22, 2024

Or should we have a fancier dispatching mechanism instead? Like if (std::is_foating_point_v<T> && std::distance(first, last) > 100), something like that?

@danlark1
Copy link
Contributor

danlark1 commented Oct 22, 2024

We can but I would recommend enabling previous behavior first and then we can refine the threshold. Also in practice, microbenchmarks can lie as the memory is hot and even for low hundred elements we can see improvements because we remove branches.

For example, when the first version with the branch free partitioning was rolled out at Google, we saw 10-15% improvement fleetwide even though microbenchmarks were saying much more.

Unfortunately, we missed the regression from the last year by accident. :(

@danlark1
Copy link
Contributor

@philnik777, please go ahead and resubmit the patch, it no longer blocks us and it fixes a long time bug. Thank you a lot for the work

philnik777 added a commit to philnik777/llvm-project that referenced this pull request Oct 29, 2024
philnik777 added a commit that referenced this pull request Oct 30, 2024
…4902)" (#114023)

This reverts commit ef44e46. The patch was originally reverted
because it was
deemed to introduce a performance regression for small inputs, however
it also fixed
a previous performance regression for larger inputs. So overall, this
patch is desirable.
NoumanAmir657 pushed a commit to NoumanAmir657/llvm-project that referenced this pull request Nov 4, 2024
…m#104902)" (llvm#114023)

This reverts commit ef44e46. The patch was originally reverted
because it was
deemed to introduce a performance regression for small inputs, however
it also fixed
a previous performance regression for larger inputs. So overall, this
patch is desirable.
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.

8 participants