[libcxx-commits] [libcxx] [libc++] Speed-up the reduction by up to 4X by breaking the dependency chain (PR #234887)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sat Oct 24 04:01:24 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Michael G. Kazakov (mikekazakov)
<details>
<summary>Changes</summary>
This PR changes `std::reduce`/`std::transform_reduce()` to process 4 elements per iteration and perform reduction in a tree-like manner:
```
__init--------------\
(X) -> __init
__first[0]\ /
(X)\ /
__first[1]/ \ /
(X)/
__first[2]\ /
(X)/
__first[3]/
```
This breaks up the dependency chain to allow the CPU to perform more speculative execution and to utilise more execution units.
On Apple M5 this leads to ~4X speedup in the tested benchmarks.
Switching to 8-way reduction brings up to ~6X speedup in some benchmarks, but can also degrade benchmarks with trivial operators where previously the compiler could vectorise the computation, so the 4-way reduction is chosen as a safe option.
Of course, in the case of very compute-costly reduction operators, the effect of this manual unrolling would be lower.
This optimisation applies only when random-access iterators are passed into `std::reduce`/`std::transform_reduce()`.
---
Full diff: https://github.com/llvm/llvm-project/pull/234887.diff
2 Files Affected:
- (modified) libcxx/include/__numeric/reduce.h (+6-2)
- (modified) libcxx/include/__numeric/transform_reduce.h (+15-2)
``````````diff
diff --git a/libcxx/include/__numeric/reduce.h b/libcxx/include/__numeric/reduce.h
index 853ca6fb61860..fe245e9097801 100644
--- a/libcxx/include/__numeric/reduce.h
+++ b/libcxx/include/__numeric/reduce.h
@@ -27,8 +27,12 @@ _LIBCPP_PUSH_MACROS
_LIBCPP_BEGIN_NAMESPACE_STD
template <class _InputIterator, class _Tp, class _BinaryOp>
-[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
-_LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp reduce(_InputIterator __first, _InputIterator __last, _Tp __init, _BinaryOp __b) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp
+reduce(_InputIterator __first, _InputIterator __last, _Tp __init, _BinaryOp __b) {
+ if constexpr (__has_random_access_iterator_category<_InputIterator>::value) {
+ for (; __last - __first >= 4; __first += 4)
+ __init = __b(std::move(__init), __b(__b(__first[0], __first[1]), __b(__first[2], __first[3])));
+ }
for (; __first != __last; ++__first)
__init = __b(std::move(__init), *__first);
return __init;
diff --git a/libcxx/include/__numeric/transform_reduce.h b/libcxx/include/__numeric/transform_reduce.h
index 53ed6aaf32d1e..66428b328ae5f 100644
--- a/libcxx/include/__numeric/transform_reduce.h
+++ b/libcxx/include/__numeric/transform_reduce.h
@@ -12,6 +12,7 @@
#include <__config>
#include <__functional/operations.h>
+#include <__iterator/iterator_traits.h>
#include <__utility/move.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -26,8 +27,13 @@ _LIBCPP_PUSH_MACROS
_LIBCPP_BEGIN_NAMESPACE_STD
template <class _InputIterator, class _Tp, class _BinaryOp, class _UnaryOp>
-[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
-_Tp transform_reduce(_InputIterator __first, _InputIterator __last, _Tp __init, _BinaryOp __b, _UnaryOp __u) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp
+transform_reduce(_InputIterator __first, _InputIterator __last, _Tp __init, _BinaryOp __b, _UnaryOp __u) {
+ if constexpr (__has_random_access_iterator_category<_InputIterator>::value) {
+ for (; __last - __first >= 4; __first += 4)
+ __init =
+ __b(std::move(__init), __b(__b(__u(__first[0]), __u(__first[1])), __b(__u(__first[2]), __u(__first[3]))));
+ }
for (; __first != __last; ++__first)
__init = __b(std::move(__init), __u(*__first));
return __init;
@@ -41,6 +47,13 @@ template <class _InputIterator1, class _InputIterator2, class _Tp, class _Binary
_Tp __init,
_BinaryOp1 __b1,
_BinaryOp2 __b2) {
+ if constexpr (__has_random_access_iterator_category<_InputIterator1>::value &&
+ __has_random_access_iterator_category<_InputIterator2>::value) {
+ for (; __last1 - __first1 >= 4; __first1 += 4, (void)(__first2 += 4))
+ __init = __b1(std::move(__init),
+ __b1(__b1(__b2(__first1[0], __first2[0]), __b2(__first1[1], __first2[1])),
+ __b1(__b2(__first1[2], __first2[2]), __b2(__first1[3], __first2[3]))));
+ }
for (; __first1 != __last1; ++__first1, (void)++__first2)
__init = __b1(std::move(__init), __b2(*__first1, *__first2));
return __init;
``````````
</details>
https://github.com/llvm/llvm-project/pull/234887
More information about the libcxx-commits
mailing list