Eigen  5.0.1
 
Loading...
Searching...
No Matches
Redux.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
5// Copyright (C) 2006-2008 Benoit Jacob <jacob.benoit.1@gmail.com>
6//
7// This Source Code Form is subject to the terms of the Mozilla
8// Public License v. 2.0. If a copy of the MPL was not distributed
9// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
10// SPDX-License-Identifier: MPL-2.0
11
12#ifndef EIGEN_REDUX_H
13#define EIGEN_REDUX_H
14
15// IWYU pragma: private
16#include "./InternalHeaderCheck.h"
17
18namespace Eigen {
19
20namespace internal {
21
22// TODO
23// * implement other kind of vectorization
24// * factorize code
25
26/***************************************************************************
27 * Part 1 : the logic deciding a strategy for vectorization and unrolling
28 ***************************************************************************/
29
30// Bounds used only to exclude unreachable reduction paths. Combining expression
31// storage bounds instead would change PlainObject types and can create oversized inline storage.
32template <typename Xpr>
33struct redux_max_size {
34 static constexpr int Rows = Xpr::MaxRowsAtCompileTime;
35 static constexpr int Cols = Xpr::MaxColsAtCompileTime;
36 static constexpr int Size = Xpr::MaxSizeAtCompileTime;
37};
38
39template <typename Op, typename Lhs, typename Rhs>
40struct redux_max_size<CwiseBinaryOp<Op, Lhs, Rhs>> {
41 using Left = redux_max_size<remove_all_t<Lhs>>;
42 using Right = redux_max_size<remove_all_t<Rhs>>;
43 static constexpr int Rows = min_size_prefer_fixed(Left::Rows, Right::Rows);
44 static constexpr int Cols = min_size_prefer_fixed(Left::Cols, Right::Cols);
45 static constexpr int Size =
46 min_size_prefer_fixed(min_size_prefer_fixed(Left::Size, Right::Size), size_at_compile_time(Rows, Cols));
47};
48
49template <typename Op, typename Arg>
50struct redux_max_size<CwiseUnaryOp<Op, Arg>> : redux_max_size<remove_all_t<Arg>> {};
51
52template <typename Arg>
53struct redux_max_size<ArrayWrapper<Arg>> : redux_max_size<remove_all_t<Arg>> {};
54
55template <typename Arg>
56struct redux_max_size<MatrixWrapper<Arg>> : redux_max_size<remove_all_t<Arg>> {};
57
58template <typename Op, typename Arg1, typename Arg2, typename Arg3>
59struct redux_max_size<CwiseTernaryOp<Op, Arg1, Arg2, Arg3>> {
60 using First = redux_max_size<remove_all_t<Arg1>>;
61 using Second = redux_max_size<remove_all_t<Arg2>>;
62 using Third = redux_max_size<remove_all_t<Arg3>>;
63 static constexpr int Rows = min_size_prefer_fixed(First::Rows, min_size_prefer_fixed(Second::Rows, Third::Rows));
64 static constexpr int Cols = min_size_prefer_fixed(First::Cols, min_size_prefer_fixed(Second::Cols, Third::Cols));
65 static constexpr int Size =
66 min_size_prefer_fixed(min_size_prefer_fixed(First::Size, min_size_prefer_fixed(Second::Size, Third::Size)),
67 size_at_compile_time(Rows, Cols));
68};
69
70template <typename Func, typename Evaluator>
71struct redux_traits {
72 public:
73 using PacketType = typename find_best_packet<typename Evaluator::Scalar, Evaluator::SizeAtCompileTime>::type;
74 enum {
75 PacketSize = unpacket_traits<PacketType>::size,
76 InnerMaxSize = int(Evaluator::IsRowMajor) ? Evaluator::MaxColsAtCompileTime : Evaluator::MaxRowsAtCompileTime,
77 OuterMaxSize = int(Evaluator::IsRowMajor) ? Evaluator::MaxRowsAtCompileTime : Evaluator::MaxColsAtCompileTime,
78 SliceVectorizedWork = int(InnerMaxSize) == Dynamic ? Dynamic
79 : int(OuterMaxSize) == Dynamic ? (int(InnerMaxSize) >= int(PacketSize) ? Dynamic : 0)
80 : (int(InnerMaxSize) / int(PacketSize)) * int(OuterMaxSize)
81 };
82
83 enum {
84 MayLinearize = (int(Evaluator::Flags) & LinearAccessBit),
85 MightVectorize = (int(Evaluator::Flags) & ActualPacketAccessBit) && (functor_traits<Func>::PacketAccess),
86 MayLinearVectorize = bool(MightVectorize) && bool(MayLinearize),
87 MaySliceVectorize = bool(MightVectorize) && (int(SliceVectorizedWork) == Dynamic || int(SliceVectorizedWork) >= 3)
88 };
89
90 public:
91 enum {
92 Traversal = int(MayLinearVectorize) ? int(LinearVectorizedTraversal)
93 : int(MaySliceVectorize) ? int(SliceVectorizedTraversal)
94 : int(MayLinearize) ? int(LinearTraversal)
95 : int(DefaultTraversal)
96 };
97
98 public:
99 enum {
100 Cost = Evaluator::SizeAtCompileTime == Dynamic
101 ? HugeCost
102 : int(Evaluator::SizeAtCompileTime) * int(Evaluator::CoeffReadCost) +
103 (Evaluator::SizeAtCompileTime - 1) * functor_traits<Func>::Cost,
104 UnrollingLimit = EIGEN_UNROLLING_LIMIT * (int(Traversal) == int(DefaultTraversal) ? 1 : int(PacketSize))
105 };
106
107 public:
108 enum { Unrolling = Cost <= UnrollingLimit ? CompleteUnrolling : NoUnrolling };
109
110#ifdef EIGEN_DEBUG_ASSIGN
111 static void debug() {
112 std::cerr << "Xpr: " << typeid(typename Evaluator::XprType).name() << std::endl;
113 std::cerr.setf(std::ios::hex, std::ios::basefield);
114 EIGEN_DEBUG_VAR(Evaluator::Flags)
115 std::cerr.unsetf(std::ios::hex);
116 EIGEN_DEBUG_VAR(InnerMaxSize)
117 EIGEN_DEBUG_VAR(OuterMaxSize)
118 EIGEN_DEBUG_VAR(SliceVectorizedWork)
119 EIGEN_DEBUG_VAR(PacketSize)
120 EIGEN_DEBUG_VAR(MightVectorize)
121 EIGEN_DEBUG_VAR(MayLinearVectorize)
122 EIGEN_DEBUG_VAR(MaySliceVectorize)
123 std::cerr << "Traversal"
124 << " = " << Traversal << " (" << demangle_traversal(Traversal) << ")" << std::endl;
125 EIGEN_DEBUG_VAR(UnrollingLimit)
126 std::cerr << "Unrolling"
127 << " = " << Unrolling << " (" << demangle_unrolling(Unrolling) << ")" << std::endl;
128 std::cerr << std::endl;
129 }
130#endif
131};
132
133/***************************************************************************
134 * Part 2 : unrollers
135 ***************************************************************************/
136
137/*** no vectorization ***/
138
139template <typename Func, typename Evaluator, Index Start, Index Length>
140struct redux_novec_unroller {
141 static constexpr Index HalfLength = Length / 2;
142
143 using Scalar = typename Evaluator::Scalar;
144
145 EIGEN_DEVICE_FUNC static constexpr EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func& func) {
146 return func(redux_novec_unroller<Func, Evaluator, Start, HalfLength>::run(eval, func),
147 redux_novec_unroller<Func, Evaluator, Start + HalfLength, Length - HalfLength>::run(eval, func));
148 }
149};
150
151template <typename Func, typename Evaluator, Index Start>
152struct redux_novec_unroller<Func, Evaluator, Start, 1> {
153 static constexpr Index outer = Start / Evaluator::InnerSizeAtCompileTime;
154 static constexpr Index inner = Start % Evaluator::InnerSizeAtCompileTime;
155
156 using Scalar = typename Evaluator::Scalar;
157
158 EIGEN_DEVICE_FUNC static constexpr EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func&) {
159 return eval.coeffByOuterInner(outer, inner);
160 }
161};
162
163// This is actually dead code and will never be called. It is required
164// to prevent false warnings regarding failed inlining though
165// for 0 length run() will never be called at all.
166template <typename Func, typename Evaluator, Index Start>
167struct redux_novec_unroller<Func, Evaluator, Start, 0> {
168 using Scalar = typename Evaluator::Scalar;
169 EIGEN_DEVICE_FUNC static constexpr EIGEN_STRONG_INLINE Scalar run(const Evaluator&, const Func&) { return Scalar(); }
170};
171
172template <typename Func, typename Evaluator, Index Start, Index Length>
173struct redux_novec_linear_unroller {
174 static constexpr Index HalfLength = Length / 2;
175
176 using Scalar = typename Evaluator::Scalar;
177
178 EIGEN_DEVICE_FUNC static constexpr EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func& func) {
179 return func(redux_novec_linear_unroller<Func, Evaluator, Start, HalfLength>::run(eval, func),
180 redux_novec_linear_unroller<Func, Evaluator, Start + HalfLength, Length - HalfLength>::run(eval, func));
181 }
182};
183
184template <typename Func, typename Evaluator, Index Start>
185struct redux_novec_linear_unroller<Func, Evaluator, Start, 1> {
186 using Scalar = typename Evaluator::Scalar;
187
188 EIGEN_DEVICE_FUNC static constexpr EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func&) {
189 return eval.coeff(Start);
190 }
191};
192
193// This is actually dead code and will never be called. It is required
194// to prevent false warnings regarding failed inlining though
195// for 0 length run() will never be called at all.
196template <typename Func, typename Evaluator, Index Start>
197struct redux_novec_linear_unroller<Func, Evaluator, Start, 0> {
198 using Scalar = typename Evaluator::Scalar;
199 EIGEN_DEVICE_FUNC static constexpr EIGEN_STRONG_INLINE Scalar run(const Evaluator&, const Func&) { return Scalar(); }
200};
201
202/*** vectorization ***/
203
204template <typename Func, typename Evaluator, Index Start, Index Length>
205struct redux_vec_unroller {
206 template <typename PacketType>
207 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE PacketType run(const Evaluator& eval, const Func& func) {
208 constexpr Index HalfLength = Length / 2;
209
210 return func.packetOp(
211 redux_vec_unroller<Func, Evaluator, Start, HalfLength>::template run<PacketType>(eval, func),
212 redux_vec_unroller<Func, Evaluator, Start + HalfLength, Length - HalfLength>::template run<PacketType>(eval,
213 func));
214 }
215};
216
217template <typename Func, typename Evaluator, Index Start>
218struct redux_vec_unroller<Func, Evaluator, Start, 1> {
219 template <typename PacketType>
220 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE PacketType run(const Evaluator& eval, const Func&) {
221 constexpr Index PacketSize = unpacket_traits<PacketType>::size;
222 constexpr Index index = Start * PacketSize;
223 constexpr Index outer = index / int(Evaluator::InnerSizeAtCompileTime);
224 constexpr Index inner = index % int(Evaluator::InnerSizeAtCompileTime);
225 constexpr int alignment = Evaluator::Alignment;
226
227 return eval.template packetByOuterInner<alignment, PacketType>(outer, inner);
228 }
229};
230
231template <typename Func, typename Evaluator, Index Start, Index Length>
232struct redux_vec_linear_unroller {
233 template <typename PacketType>
234 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE PacketType run(const Evaluator& eval, const Func& func) {
235 constexpr Index HalfLength = Length / 2;
236
237 return func.packetOp(
238 redux_vec_linear_unroller<Func, Evaluator, Start, HalfLength>::template run<PacketType>(eval, func),
239 redux_vec_linear_unroller<Func, Evaluator, Start + HalfLength, Length - HalfLength>::template run<PacketType>(
240 eval, func));
241 }
242};
243
244template <typename Func, typename Evaluator, Index Start>
245struct redux_vec_linear_unroller<Func, Evaluator, Start, 1> {
246 template <typename PacketType>
247 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE PacketType run(const Evaluator& eval, const Func&) {
248 constexpr Index PacketSize = unpacket_traits<PacketType>::size;
249 constexpr Index index = (Start * PacketSize);
250 constexpr int alignment = Evaluator::Alignment;
251 return eval.template packet<alignment, PacketType>(index);
252 }
253};
254
255/***************************************************************************
256 * Part 3 : implementation of all cases
257 ***************************************************************************/
258
259template <typename Func, typename Evaluator, int Traversal = redux_traits<Func, Evaluator>::Traversal,
260 int Unrolling = redux_traits<Func, Evaluator>::Unrolling>
261struct redux_impl;
262
263// Cutoffs below which the plain serial loop beats the wider unrolled bodies, measured on x86-64
264// with GCC 13 and Clang 18. The linear path serves both contiguous data (vectorizes, profits
265// from ~24) and strided data (loads dominate, profits only from ~64); 32 is where neither side
266// loses measurably.
267constexpr Index kReduxCommutativeCutoff = 32; // independent accumulators, linear traversal
268constexpr Index kReduxCommutativeInnerCutoff = 16; // independent accumulators, outer/inner traversal
269// GCC auto-vectorizes the ordered tree through a shuffle network whose setup only amortizes on
270// long runs; Clang keeps it scalar, where the shorter dependency chain pays from small sizes.
271constexpr Index kReduxOrderedTreeCutoff = EIGEN_COMP_GNUC_STRICT ? 192 : 16;
272
273template <typename Func, typename Evaluator>
274struct redux_impl<Func, Evaluator, DefaultTraversal, NoUnrolling> {
275 using Scalar = typename Evaluator::Scalar;
276
277 template <typename XprType>
278 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func& func, const XprType& xpr) {
279 eigen_assert(xpr.rows() > 0 && xpr.cols() > 0 && "you are using an empty matrix");
280 const Index innerSize = xpr.innerSize();
281 const Index outerSize = xpr.outerSize();
282 using Bounds = redux_max_size<XprType>;
283 constexpr int MaxInnerSize = XprType::IsVectorAtCompileTime ? Bounds::Size
284 : XprType::IsRowMajor ? Bounds::Cols
285 : Bounds::Rows;
286 EIGEN_IF_CONSTEXPR (functor_is_commutative<Func>::value) {
287 EIGEN_IF_CONSTEXPR (MaxInnerSize == Dynamic || MaxInnerSize >= kReduxCommutativeInnerCutoff) {
288 if (innerSize >= kReduxCommutativeInnerCutoff) return runCommutative(eval, func, innerSize, outerSize);
289 }
290 } else {
291 EIGEN_IF_CONSTEXPR (MaxInnerSize == Dynamic || MaxInnerSize >= kReduxOrderedTreeCutoff) {
292 if (innerSize >= kReduxOrderedTreeCutoff) return runOrderedTree(eval, func, innerSize, outerSize);
293 }
294 }
295 Scalar res = eval.coeffByOuterInner(0, 0);
296 for (Index j = 1; j < innerSize; ++j) res = func(res, eval.coeffByOuterInner(0, j));
297 for (Index i = 1; i < outerSize; ++i)
298 for (Index j = 0; j < innerSize; ++j) res = func(res, eval.coeffByOuterInner(i, j));
299 return res;
300 }
301
302 // Commutativity lets coefficients split across eight independent accumulators: the dependency
303 // chain drops to size/8 and each stride-8 stream vectorizes without cross-lane shuffles. The
304 // accumulators persist across outer slices; only the ragged inner tail of each slice joins a0.
305 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar runCommutative(const Evaluator& eval, const Func& func,
306 Index innerSize, Index outerSize) {
307 Scalar a0 = eval.coeffByOuterInner(0, 0), a1 = eval.coeffByOuterInner(0, 1);
308 Scalar a2 = eval.coeffByOuterInner(0, 2), a3 = eval.coeffByOuterInner(0, 3);
309 Scalar a4 = eval.coeffByOuterInner(0, 4), a5 = eval.coeffByOuterInner(0, 5);
310 Scalar a6 = eval.coeffByOuterInner(0, 6), a7 = eval.coeffByOuterInner(0, 7);
311 const Index unrolledEnd = innerSize - innerSize % 8;
312 for (Index i = 0; i < outerSize; ++i) {
313 Index j = (i == 0) ? 8 : 0;
314 for (; j < unrolledEnd; j += 8) {
315 a0 = func(a0, eval.coeffByOuterInner(i, j + 0));
316 a1 = func(a1, eval.coeffByOuterInner(i, j + 1));
317 a2 = func(a2, eval.coeffByOuterInner(i, j + 2));
318 a3 = func(a3, eval.coeffByOuterInner(i, j + 3));
319 a4 = func(a4, eval.coeffByOuterInner(i, j + 4));
320 a5 = func(a5, eval.coeffByOuterInner(i, j + 5));
321 a6 = func(a6, eval.coeffByOuterInner(i, j + 6));
322 a7 = func(a7, eval.coeffByOuterInner(i, j + 7));
323 }
324 for (; j < innerSize; ++j) a0 = func(a0, eval.coeffByOuterInner(i, j));
325 }
326 return func(func(func(a0, a1), func(a2, a3)), func(func(a4, a5), func(a6, a7)));
327 }
328
329 // Associativity alone: contiguous groups of four combine in traversal order through a pairwise
330 // tree, shortening the dependency chain to size/4 without reordering any operands.
331 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar runOrderedTree(const Evaluator& eval, const Func& func,
332 Index innerSize, Index outerSize) {
333 const Index unrolledEnd = innerSize - innerSize % 4;
334 Scalar res = reduce4(eval, func, 0, 0);
335 for (Index i = 0; i < outerSize; ++i) {
336 Index j = (i == 0) ? 4 : 0;
337 for (; j < unrolledEnd; j += 4) res = func(res, reduce4(eval, func, i, j));
338 for (; j < innerSize; ++j) res = func(res, eval.coeffByOuterInner(i, j));
339 }
340 return res;
341 }
342
343 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar reduce4(const Evaluator& eval, const Func& func, Index outer,
344 Index inner) {
345 return func(func(eval.coeffByOuterInner(outer, inner + 0), eval.coeffByOuterInner(outer, inner + 1)),
346 func(eval.coeffByOuterInner(outer, inner + 2), eval.coeffByOuterInner(outer, inner + 3)));
347 }
348};
349
350template <typename Func, typename Evaluator>
351struct redux_impl<Func, Evaluator, LinearTraversal, NoUnrolling> {
352 using Scalar = typename Evaluator::Scalar;
353
354 template <typename XprType>
355 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func& func, const XprType& xpr) {
356 const Index size = xpr.size();
357 eigen_assert(size > 0 && "you are using an empty matrix");
358 // Do not generate wide reduction bodies for bounded expressions that cannot
359 // reach their cutoff (GCC can otherwise diagnose their unreachable loads).
360 EIGEN_IF_CONSTEXPR (functor_is_commutative<Func>::value) {
361 EIGEN_IF_CONSTEXPR (redux_max_size<XprType>::Size == Dynamic ||
362 redux_max_size<XprType>::Size >= kReduxCommutativeCutoff) {
363 if (size >= kReduxCommutativeCutoff) return runCommutative(eval, func, size);
364 }
365 } else {
366 EIGEN_IF_CONSTEXPR (redux_max_size<XprType>::Size == Dynamic ||
367 redux_max_size<XprType>::Size >= kReduxOrderedTreeCutoff) {
368 if (size >= kReduxOrderedTreeCutoff) return runOrderedTree(eval, func, size);
369 }
370 }
371 Scalar res = eval.coeff(0);
372 for (Index k = 1; k < size; ++k) res = func(res, eval.coeff(k));
373 return res;
374 }
375
376 // Commutativity lets coefficients split across eight independent accumulators: the dependency
377 // chain drops to size/8 and each stride-8 stream vectorizes without cross-lane shuffles.
378 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar runCommutative(const Evaluator& eval, const Func& func,
379 Index size) {
380 Scalar a0 = eval.coeff(0), a1 = eval.coeff(1), a2 = eval.coeff(2), a3 = eval.coeff(3);
381 Scalar a4 = eval.coeff(4), a5 = eval.coeff(5), a6 = eval.coeff(6), a7 = eval.coeff(7);
382 const Index unrolledEnd = size - size % 8;
383 Index k = 8;
384 for (; k < unrolledEnd; k += 8) {
385 a0 = func(a0, eval.coeff(k + 0));
386 a1 = func(a1, eval.coeff(k + 1));
387 a2 = func(a2, eval.coeff(k + 2));
388 a3 = func(a3, eval.coeff(k + 3));
389 a4 = func(a4, eval.coeff(k + 4));
390 a5 = func(a5, eval.coeff(k + 5));
391 a6 = func(a6, eval.coeff(k + 6));
392 a7 = func(a7, eval.coeff(k + 7));
393 }
394 Scalar res = func(func(func(a0, a1), func(a2, a3)), func(func(a4, a5), func(a6, a7)));
395 for (; k < size; ++k) res = func(res, eval.coeff(k));
396 return res;
397 }
398
399 // Associativity alone: contiguous groups of four combine in traversal order through a pairwise
400 // tree, shortening the dependency chain to size/4 without reordering any operands.
401 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar runOrderedTree(const Evaluator& eval, const Func& func,
402 Index size) {
403 Scalar res = func(func(eval.coeff(0), eval.coeff(1)), func(eval.coeff(2), eval.coeff(3)));
404 const Index unrolledEnd = size - size % 4;
405 Index k = 4;
406 for (; k < unrolledEnd; k += 4) {
407 res = func(res, func(func(eval.coeff(k), eval.coeff(k + 1)), func(eval.coeff(k + 2), eval.coeff(k + 3))));
408 }
409 for (; k < size; ++k) res = func(res, eval.coeff(k));
410 return res;
411 }
412};
414template <typename Func, typename Evaluator>
415struct redux_impl<Func, Evaluator, DefaultTraversal, CompleteUnrolling>
416 : redux_novec_unroller<Func, Evaluator, 0, Evaluator::SizeAtCompileTime> {
417 using Base = redux_novec_unroller<Func, Evaluator, 0, Evaluator::SizeAtCompileTime>;
418 using Scalar = typename Evaluator::Scalar;
419 template <typename XprType>
420 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func& func,
421 const XprType& /*xpr*/) {
422 return Base::run(eval, func);
424};
425
426template <typename Func, typename Evaluator>
427struct redux_impl<Func, Evaluator, LinearTraversal, CompleteUnrolling>
428 : redux_novec_linear_unroller<Func, Evaluator, 0, Evaluator::SizeAtCompileTime> {
429 using Base = redux_novec_linear_unroller<Func, Evaluator, 0, Evaluator::SizeAtCompileTime>;
430 using Scalar = typename Evaluator::Scalar;
431 template <typename XprType>
432 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func& func,
433 const XprType& /*xpr*/) {
434 return Base::run(eval, func);
435 }
436};
437
438template <typename Func, typename Evaluator>
439struct redux_impl<Func, Evaluator, LinearVectorizedTraversal, NoUnrolling> {
440 using Scalar = typename Evaluator::Scalar;
441 using PacketScalar = typename redux_traits<Func, Evaluator>::PacketType;
442
443 template <typename XprType>
444 static Scalar run(const Evaluator& eval, const Func& func, const XprType& xpr) {
445 const Index size = xpr.size();
446
447 constexpr Index packetSize = redux_traits<Func, Evaluator>::PacketSize;
448 constexpr int packetAlignment = unpacket_traits<PacketScalar>::alignment;
449 constexpr int alignment0 =
450 (bool(Evaluator::Flags & DirectAccessBit) && bool(packet_traits<Scalar>::AlignedOnScalar))
451 ? int(packetAlignment)
452 : int(Unaligned);
453 constexpr int alignment = plain_enum_max(alignment0, Evaluator::Alignment);
454 const Index alignedStart = internal::first_default_aligned(xpr);
455 const Index alignedSize4 = numext::round_down(size - alignedStart, 4 * packetSize);
456 const Index alignedSize = numext::round_down(size - alignedStart, packetSize);
457 const Index alignedEnd4 = alignedStart + alignedSize4;
458 const Index alignedEnd = alignedStart + alignedSize;
459 Scalar res;
460 constexpr Index maxSize = redux_max_size<XprType>::Size;
461 EIGEN_IF_CONSTEXPR (maxSize == Dynamic || maxSize >= packetSize) {
462 if (alignedSize) {
463 PacketScalar packet_res0 = eval.template packet<alignment, PacketScalar>(alignedStart);
464 EIGEN_IF_CONSTEXPR (maxSize == Dynamic || maxSize >= 4 * packetSize) {
465 if (alignedSize4) // four independent accumulators keep the loop off the packetOp latency chain
466 {
467 PacketScalar packet_res1 = eval.template packet<alignment, PacketScalar>(alignedStart + packetSize);
468 PacketScalar packet_res2 = eval.template packet<alignment, PacketScalar>(alignedStart + 2 * packetSize);
469 PacketScalar packet_res3 = eval.template packet<alignment, PacketScalar>(alignedStart + 3 * packetSize);
470 for (Index index = alignedStart + 4 * packetSize; index < alignedEnd4; index += 4 * packetSize) {
471 packet_res0 = func.packetOp(packet_res0, eval.template packet<alignment, PacketScalar>(index));
472 packet_res1 =
473 func.packetOp(packet_res1, eval.template packet<alignment, PacketScalar>(index + packetSize));
474 packet_res2 =
475 func.packetOp(packet_res2, eval.template packet<alignment, PacketScalar>(index + 2 * packetSize));
476 packet_res3 =
477 func.packetOp(packet_res3, eval.template packet<alignment, PacketScalar>(index + 3 * packetSize));
478 }
479
480 // The one to three leftover packets go into accumulators that are still independent, so they
481 // cost a packetOp each rather than extending the merge below.
482 const Index remSize = alignedSize - alignedSize4;
483 if (remSize >= packetSize) {
484 packet_res0 = func.packetOp(packet_res0, eval.template packet<alignment, PacketScalar>(alignedEnd4));
485 if (remSize >= 2 * packetSize) {
486 packet_res1 =
487 func.packetOp(packet_res1, eval.template packet<alignment, PacketScalar>(alignedEnd4 + packetSize));
488 if (remSize == 3 * packetSize)
489 packet_res2 = func.packetOp(
490 packet_res2, eval.template packet<alignment, PacketScalar>(alignedEnd4 + 2 * packetSize));
491 }
492 }
493
494 // Merge as (res0 + res1) + (res2 + res3): two packetOp latencies deep instead of three.
495 packet_res0 = func.packetOp(packet_res0, packet_res1);
496 packet_res2 = func.packetOp(packet_res2, packet_res3);
497 packet_res0 = func.packetOp(packet_res0, packet_res2);
498 }
499 }
500 EIGEN_IF_CONSTEXPR (maxSize == Dynamic || maxSize >= 2 * packetSize) {
501 if (!alignedSize4 && alignedSize > packetSize) {
502 // Two or three packets: straight-line, with none of the trip-count setup a loop would need.
503 packet_res0 =
504 func.packetOp(packet_res0, eval.template packet<alignment, PacketScalar>(alignedStart + packetSize));
505 EIGEN_IF_CONSTEXPR (maxSize == Dynamic || maxSize >= 3 * packetSize) {
506 if (alignedSize > 2 * packetSize)
507 packet_res0 = func.packetOp(
508 packet_res0, eval.template packet<alignment, PacketScalar>(alignedStart + 2 * packetSize));
509 }
510 }
511 }
512 res = func.predux(packet_res0);
513
514 for (Index index = 0; index < alignedStart; ++index) res = func(res, eval.coeff(index));
515
516 for (Index index = alignedEnd; index < size; ++index) res = func(res, eval.coeff(index));
517 return res;
518 }
519 }
520 // Too small to vectorize anything.
521 res = eval.coeff(0);
522 for (Index index = 1; index < size; ++index) res = func(res, eval.coeff(index));
523 return res;
524 }
525};
526
527// NOTE: for SliceVectorizedTraversal we simply bypass unrolling
528template <typename Func, typename Evaluator, int Unrolling>
529struct redux_impl<Func, Evaluator, SliceVectorizedTraversal, Unrolling> {
530 using Scalar = typename Evaluator::Scalar;
531 using PacketType = typename redux_traits<Func, Evaluator>::PacketType;
532
533 static constexpr Index PacketSize = redux_traits<Func, Evaluator>::PacketSize;
534
535 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE PacketType packetAt(const Evaluator& eval, Index j, Index i) {
536 return eval.template packetByOuterInner<Unaligned, PacketType>(j, i);
537 }
538
539 template <typename XprType>
540 EIGEN_DEVICE_FUNC static Scalar run(const Evaluator& eval, const Func& func, const XprType& xpr) {
541 eigen_assert(xpr.rows() > 0 && xpr.cols() > 0 && "you are using an empty matrix");
542 const Index innerSize = xpr.innerSize();
543 const Index outerSize = xpr.outerSize();
544 const Index packetedInnerSize = numext::round_down(innerSize, PacketSize);
545 const Index quadInnerSize = numext::round_down(innerSize, 4 * PacketSize);
546 Scalar res;
547 if (packetedInnerSize) {
548 // A single accumulator leaves one packetOp latency between consecutive iterations, so carry
549 // four of them across the whole (j, i) traversal and merge them once.
550 PacketType packet_res0 = packetAt(eval, 0, 0);
551 if (quadInnerSize) {
552 // Four packets or more per panel: each accumulator takes one packet of every group of four.
553 PacketType packet_res1 = packetAt(eval, 0, PacketSize);
554 PacketType packet_res2 = packetAt(eval, 0, 2 * PacketSize);
555 PacketType packet_res3 = packetAt(eval, 0, 3 * PacketSize);
556 // Every panel has the same number of packets left over, so this is loop-invariant and the
557 // branches on it do not wait on the packet loop below.
558 const Index remSize = packetedInnerSize - quadInnerSize;
559 for (Index j = 0; j < outerSize; ++j) {
560 for (Index i = (j == 0) ? 4 * PacketSize : 0; i < quadInnerSize; i += 4 * PacketSize) {
561 packet_res0 = func.packetOp(packet_res0, packetAt(eval, j, i));
562 packet_res1 = func.packetOp(packet_res1, packetAt(eval, j, i + PacketSize));
563 packet_res2 = func.packetOp(packet_res2, packetAt(eval, j, i + 2 * PacketSize));
564 packet_res3 = func.packetOp(packet_res3, packetAt(eval, j, i + 3 * PacketSize));
565 }
566 // This panel's one to three leftover packets, into accumulators that are still
567 // independent of each other.
568 if (remSize >= PacketSize) {
569 packet_res0 = func.packetOp(packet_res0, packetAt(eval, j, quadInnerSize));
570 if (remSize >= 2 * PacketSize) {
571 packet_res1 = func.packetOp(packet_res1, packetAt(eval, j, quadInnerSize + PacketSize));
572 if (remSize == 3 * PacketSize)
573 packet_res2 = func.packetOp(packet_res2, packetAt(eval, j, quadInnerSize + 2 * PacketSize));
574 }
575 }
576 }
577 // Merge as (res0 + res1) + (res2 + res3): two packetOp latencies deep instead of three.
578 packet_res0 = func.packetOp(packet_res0, packet_res1);
579 packet_res2 = func.packetOp(packet_res2, packet_res3);
580 packet_res0 = func.packetOp(packet_res0, packet_res2);
581 } else if (outerSize >= 4) {
582 // Panels of one to three packets cannot supply four independent packets, so the
583 // independence comes from the outer dimension: each accumulator takes its own panel.
584 PacketType packet_res1 = packetAt(eval, 1, 0);
585 PacketType packet_res2 = packetAt(eval, 2, 0);
586 PacketType packet_res3 = packetAt(eval, 3, 0);
587 for (Index i = PacketSize; i < packetedInnerSize; i += PacketSize) {
588 packet_res0 = func.packetOp(packet_res0, packetAt(eval, 0, i));
589 packet_res1 = func.packetOp(packet_res1, packetAt(eval, 1, i));
590 packet_res2 = func.packetOp(packet_res2, packetAt(eval, 2, i));
591 packet_res3 = func.packetOp(packet_res3, packetAt(eval, 3, i));
592 }
593 // Both loops take their bounds from quadOuterSize rather than sharing j: for a fixed outer size, GCC 10
594 // otherwise emits -Waggressive-loop-optimizations for the tail loop, which never runs.
595 const Index quadOuterSize = numext::round_down(outerSize, 4);
596 for (Index j = 4; j < quadOuterSize; j += 4)
597 for (Index i = 0; i < packetedInnerSize; i += PacketSize) {
598 packet_res0 = func.packetOp(packet_res0, packetAt(eval, j, i));
599 packet_res1 = func.packetOp(packet_res1, packetAt(eval, j + 1, i));
600 packet_res2 = func.packetOp(packet_res2, packetAt(eval, j + 2, i));
601 packet_res3 = func.packetOp(packet_res3, packetAt(eval, j + 3, i));
602 }
603 for (Index j = quadOuterSize; j < outerSize; ++j)
604 for (Index i = 0; i < packetedInnerSize; i += PacketSize)
605 packet_res0 = func.packetOp(packet_res0, packetAt(eval, j, i));
606
607 packet_res0 = func.packetOp(packet_res0, packet_res1);
608 packet_res2 = func.packetOp(packet_res2, packet_res3);
609 packet_res0 = func.packetOp(packet_res0, packet_res2);
610 } else {
611 // Fewer than four narrow panels: at most nine packets in total, not worth a merge.
612 for (Index j = 0; j < outerSize; ++j)
613 for (Index i = (j == 0 ? PacketSize : 0); i < packetedInnerSize; i += PacketSize)
614 packet_res0 = func.packetOp(packet_res0, packetAt(eval, j, i));
615 }
616
617 res = func.predux(packet_res0);
618 for (Index j = 0; j < outerSize; ++j)
619 for (Index i = packetedInnerSize; i < innerSize; ++i) res = func(res, eval.coeffByOuterInner(j, i));
620 } else // too small to vectorize anything.
621 // since this is dynamic-size hence inefficient anyway for such small sizes, don't try to optimize.
622 {
623 res = redux_impl<Func, Evaluator, DefaultTraversal, NoUnrolling>::run(eval, func, xpr);
624 }
625
626 return res;
627 }
628};
629
630template <typename Func, typename Evaluator>
631struct redux_impl<Func, Evaluator, LinearVectorizedTraversal, CompleteUnrolling> {
632 using Scalar = typename Evaluator::Scalar;
633
634 using PacketType = typename redux_traits<Func, Evaluator>::PacketType;
635 static constexpr Index PacketSize = redux_traits<Func, Evaluator>::PacketSize;
636 static constexpr Index Size = Evaluator::SizeAtCompileTime;
637 static constexpr Index VectorizedSize = (int(Size) / int(PacketSize)) * int(PacketSize);
638
639 template <typename XprType>
640 EIGEN_DEVICE_FUNC static EIGEN_STRONG_INLINE Scalar run(const Evaluator& eval, const Func& func, const XprType& xpr) {
641 EIGEN_ONLY_USED_FOR_DEBUG(xpr);
642 eigen_assert(xpr.rows() > 0 && xpr.cols() > 0 && "you are using an empty matrix");
643 if (VectorizedSize > 0) {
644 Scalar res = func.predux(
645 redux_vec_linear_unroller<Func, Evaluator, 0, Size / PacketSize>::template run<PacketType>(eval, func));
646 if (VectorizedSize != Size)
647 res = func(
648 res, redux_novec_linear_unroller<Func, Evaluator, VectorizedSize, Size - VectorizedSize>::run(eval, func));
649 return res;
650 } else {
651 return redux_novec_linear_unroller<Func, Evaluator, 0, Size>::run(eval, func);
652 }
653 }
654};
655
656// evaluator adaptor
657template <typename XprType_>
658class redux_evaluator : public internal::evaluator<const XprType_> {
659 using Base = internal::evaluator<const XprType_>;
660
661 public:
662 using XprType = XprType_;
663 EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE explicit redux_evaluator(const XprType& xpr) : Base(xpr) {}
664
665 using Scalar = typename XprType::Scalar;
666 using CoeffReturnType = typename XprType::CoeffReturnType;
667 using PacketScalar = typename XprType::PacketScalar;
668
669 enum {
670 MaxRowsAtCompileTime = XprType::MaxRowsAtCompileTime,
671 MaxColsAtCompileTime = XprType::MaxColsAtCompileTime,
672 // TODO: we should not remove DirectAccessBit and rather find an elegant way to query the alignment offset at
673 // runtime from the evaluator
674 Flags = Base::Flags & ~DirectAccessBit,
675 IsRowMajor = XprType::IsRowMajor,
676 SizeAtCompileTime = XprType::SizeAtCompileTime,
677 InnerSizeAtCompileTime = XprType::InnerSizeAtCompileTime
678 };
679
680 EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE CoeffReturnType coeffByOuterInner(Index outer, Index inner) const {
681 return Base::coeff(IsRowMajor ? outer : inner, IsRowMajor ? inner : outer);
682 }
683
684 template <int LoadMode, typename PacketType>
685 EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PacketType packetByOuterInner(Index outer, Index inner) const {
686 return Base::template packet<LoadMode, PacketType>(IsRowMajor ? outer : inner, IsRowMajor ? inner : outer);
687 }
688
689 template <int LoadMode, typename PacketType>
690 EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE PacketType packetSegmentByOuterInner(Index outer, Index inner, Index begin,
691 Index count) const {
692 return Base::template packetSegment<LoadMode, PacketType>(IsRowMajor ? outer : inner, IsRowMajor ? inner : outer,
693 begin, count);
694 }
695};
696
697// A reduction over an expression whose inner stride is not statically 1 (e.g. a dynamic-inner-stride
698// Map/Ref, or a row of a dynamic matrix) falls back to a scalar traversal, because the evaluator
699// drops PacketAccessBit when the inner stride is unknown at compile time. Yet such expressions are
700// very often contiguous at runtime. This trait flags the cases where it is worth checking at runtime
701// whether the data is contiguous and, if so, reducing it as a contiguous vector to recover full
702// vectorization. We only bother when the expression has direct access, the functor and scalar are
703// vectorizable, and the inner stride is not already statically 1 (otherwise it is handled directly).
704template <typename Func, typename Evaluator>
705struct redux_has_runtime_unit_stride_path {
706 using XprType = typename Evaluator::XprType;
707 using Scalar = typename Evaluator::Scalar;
708 static constexpr bool value = bool(traits<XprType>::Flags & DirectAccessBit) &&
709 bool(functor_traits<Func>::PacketAccess) && bool(packet_traits<Scalar>::Vectorizable) &&
710 (int(inner_stride_at_compile_time<XprType>::value) != 1);
711};
712
713template <typename Func, typename Evaluator, typename XprType,
714 bool = redux_has_runtime_unit_stride_path<Func, Evaluator>::value>
715struct redux_dispatch {
716 static EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename Evaluator::Scalar run(const Evaluator& thisEval,
717 const Func& func, const XprType& xpr) {
718 return redux_impl<Func, Evaluator>::run(thisEval, func, xpr);
719 }
720};
721
722// Runtime contiguity fast path: when the inner stride is 1 and the data is fully packed
723// (a single inner panel, or no gap between inner panels), reduce the underlying buffer as a
724// contiguous vector. The reduction is over all coefficients with an associative functor, so
725// reducing in storage order yields the same result (up to the usual floating-point reassociation
726// already inherent to vectorized reductions).
727template <typename Func, typename Evaluator, typename XprType>
728struct redux_dispatch<Func, Evaluator, XprType, true> {
729 using Scalar = typename Evaluator::Scalar;
730 static EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar run(const Evaluator& thisEval, const Func& func,
731 const XprType& xpr) {
732 if (xpr.innerStride() == 1 && (xpr.outerSize() == 1 || xpr.outerStride() == xpr.innerSize())) {
733 using PlainVector = Matrix<Scalar, Dynamic, 1>;
734 using MapType = Map<const PlainVector, Evaluator::Alignment>;
735 MapType contiguous(xpr.data(), xpr.size());
736 redux_evaluator<MapType> mapEval(contiguous);
737 return redux_impl<Func, redux_evaluator<MapType>>::run(mapEval, func, contiguous);
738 }
739 return redux_impl<Func, Evaluator>::run(thisEval, func, xpr);
740 }
741};
742
743} // end namespace internal
744
745/***************************************************************************
746 * Part 4 : public API
747 ***************************************************************************/
748
761template <typename Derived>
762template <typename Func>
763EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename internal::traits<Derived>::Scalar DenseBase<Derived>::redux(
764 const Func& func) const {
765 eigen_assert(this->rows() > 0 && this->cols() > 0 && "you are using an empty matrix");
766
767 using ThisEvaluator = typename internal::redux_evaluator<Derived>;
768 ThisEvaluator thisEval(derived());
769
770 // The initial expression is passed to the reducer as an additional argument instead of
771 // passing it as a member of redux_evaluator. redux_dispatch additionally takes a runtime
772 // contiguity fast path for expressions that lose compile-time vectorization to a dynamic
773 // inner stride but are contiguous at runtime (see redux_dispatch).
774 return internal::redux_dispatch<Func, ThisEvaluator, Derived>::run(thisEval, func, derived());
775}
776
784template <typename Derived>
785template <int NaNPropagation>
786EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename internal::traits<Derived>::Scalar DenseBase<Derived>::minCoeff() const {
787 return derived().redux(Eigen::internal::scalar_min_op<Scalar, Scalar, NaNPropagation>());
788}
789
797template <typename Derived>
798template <int NaNPropagation>
799EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename internal::traits<Derived>::Scalar DenseBase<Derived>::maxCoeff() const {
800 return derived().redux(Eigen::internal::scalar_max_op<Scalar, Scalar, NaNPropagation>());
801}
802
809template <typename Derived>
810EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename internal::traits<Derived>::Scalar DenseBase<Derived>::sum() const {
811 EIGEN_IF_CONSTEXPR (MaxSizeAtCompileTime == 0 || SizeAtCompileTime == 0) {
812 return Scalar(0);
813 } else {
814 EIGEN_IF_CONSTEXPR (SizeAtCompileTime == Dynamic) {
815 if (size() == 0) return Scalar(0);
816 }
817 return derived().redux(Eigen::internal::scalar_sum_op<Scalar, Scalar>());
818 }
819}
820
825template <typename Derived>
826EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename internal::traits<Derived>::Scalar DenseBase<Derived>::mean() const {
827#ifdef __INTEL_COMPILER
828#pragma warning push
829#pragma warning(disable : 2259)
830#endif
831 return Scalar(derived().redux(Eigen::internal::scalar_sum_op<Scalar, Scalar>())) / Scalar(this->size());
832#ifdef __INTEL_COMPILER
833#pragma warning pop
834#endif
835}
836
844template <typename Derived>
845EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename internal::traits<Derived>::Scalar DenseBase<Derived>::prod() const {
846 EIGEN_IF_CONSTEXPR (MaxSizeAtCompileTime == 0 || SizeAtCompileTime == 0) {
847 return Scalar(1);
848 } else {
849 EIGEN_IF_CONSTEXPR (SizeAtCompileTime == Dynamic) {
850 if (size() == 0) return Scalar(1);
851 }
852 return derived().redux(Eigen::internal::scalar_product_op<Scalar>());
853 }
854}
855
862template <typename Derived>
863EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE typename internal::traits<Derived>::Scalar MatrixBase<Derived>::trace() const {
864 return derived().diagonal().sum();
865}
866
867} // end namespace Eigen
868
869#endif // EIGEN_REDUX_H
internal::traits< Derived >::Scalar minCoeff() const
Definition Redux.h:786
Scalar mean() const
Definition Redux.h:826
internal::traits< Derived >::Scalar maxCoeff() const
Definition Redux.h:799
@ SizeAtCompileTime
Definition DenseBase.h:109
@ MaxSizeAtCompileTime
Definition DenseBase.h:136
EvalReturnType eval() const
Definition DenseBase.h:385
typename internal::traits< ArrayWrapper< ExpressionType > >::Scalar Scalar
Definition DenseBase.h:63
Scalar sum() const
Definition Redux.h:810
Scalar prod() const
Definition Redux.h:845
Scalar trace() const
Definition Redux.h:863
constexpr unsigned int ActualPacketAccessBit
Definition Constants.h:109
constexpr unsigned int LinearAccessBit
Definition Constants.h:134