From 6a10b48aad46c28684ce29365dd12303e466b7aa Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 9 May 2026 18:46:21 +0200 Subject: [PATCH 1/2] Optimize Enumerable Min/Max final reduction with shuffles Replaces the scalar log-N reduction loop on the final 128-bit accumulator with a shuffle-based tree reduction (log2(Vector128.Count) compares), introducing a HorizontalMinMax helper modeled after TensorPrimitives.HorizontalAggregate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Linq/src/System/Linq/MaxMin.cs | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs index 26e1d6eec512e4..c3b82a5c913dec 100644 --- a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs +++ b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using System.Numerics; using System.Runtime.Intrinsics; using System.Runtime.CompilerServices; @@ -101,15 +102,7 @@ private static T MinMaxInteger(this IEnumerable source) } // Reduce to single value - // NOTE: this can be optimized further with shuffles. - value = best128[0]; - for (int i = 1; i < Vector128.Count; i++) - { - if (TMinMax.Compare(best128[i], value)) - { - value = best128[i]; - } - } + value = HorizontalMinMax(best128); } else { @@ -132,5 +125,43 @@ private static T MinMaxInteger(this IEnumerable source) return value; } + + /// Reduces a to a single element using . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T HorizontalMinMax(Vector128 x) + where T : struct, IBinaryInteger + where TMinMax : IMinMaxCalc + { + // Perform log2(Vector128.Count) reductions, each combining the vector with a shuffled + // copy of itself so that lane 0 ends up holding the min/max of all original lanes. + if (Vector128.Count == 16) + { + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)2, 3, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)).As()); + } + else if (Vector128.Count == 8) + { + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), Vector128.Create(4, 5, 6, 7, 0, 1, 2, 3)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), Vector128.Create(2, 3, 0, 1, 4, 5, 6, 7)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), Vector128.Create(1, 0, 2, 3, 4, 5, 6, 7)).As()); + } + else if (Vector128.Count == 4) + { + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt32(), Vector128.Create(2, 3, 0, 1)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt32(), Vector128.Create(1, 0, 3, 2)).As()); + } + else if (Vector128.Count == 2) + { + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt64(), Vector128.Create(1, 0)).As()); + } + else + { + Debug.Assert(Vector128.Count == 1); + } + + return x.ToScalar(); + } } } From ec3708d2d3e9fbd49befa0a8d462ebe4c677ba49 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 9 May 2026 19:42:28 +0200 Subject: [PATCH 2/2] cleanup --- .../System.Linq/src/System/Linq/MaxMin.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs index c3b82a5c913dec..760c70da179fc5 100644 --- a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs +++ b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs @@ -136,31 +136,34 @@ private static T HorizontalMinMax(Vector128 x) // copy of itself so that lane 0 ends up holding the min/max of all original lanes. if (Vector128.Count == 16) { - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7)).As()); - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15)).As()); - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)2, 3, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)).As()); - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), Vector128.Create((byte)1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), + Vector128.Create((byte)8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), + Vector128.Create((byte)4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), + Vector128.Create((byte)2, 3, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsByte(), + Vector128.Create((byte)1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)).As()); } else if (Vector128.Count == 8) { - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), Vector128.Create(4, 5, 6, 7, 0, 1, 2, 3)).As()); - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), Vector128.Create(2, 3, 0, 1, 4, 5, 6, 7)).As()); - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), Vector128.Create(1, 0, 2, 3, 4, 5, 6, 7)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), + Vector128.Create(4, 5, 6, 7, 0, 1, 2, 3)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), + Vector128.Create(2, 3, 0, 1, 4, 5, 6, 7)).As()); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt16(), + Vector128.Create(1, 0, 2, 3, 4, 5, 6, 7)).As()); } else if (Vector128.Count == 4) { x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt32(), Vector128.Create(2, 3, 0, 1)).As()); x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt32(), Vector128.Create(1, 0, 3, 2)).As()); } - else if (Vector128.Count == 2) - { - x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt64(), Vector128.Create(1, 0)).As()); - } else { - Debug.Assert(Vector128.Count == 1); + Debug.Assert(Vector128.Count == 2); + x = TMinMax.Compare(x, Vector128.Shuffle(x.AsInt64(), Vector128.Create(1, 0)).As()); } - return x.ToScalar(); } }