From 88e91f3586f8090613a07c7685855e29cf1960de Mon Sep 17 00:00:00 2001 From: tahakocal Date: Tue, 22 Sep 2026 13:20:06 +0300 Subject: [PATCH] Sum into separate accumulators when averaging a span of Int32 Average(IEnumerable) widens each vector of Int32 values and adds both halves into a single Vector, so the two adds of an iteration and the adds of consecutive iterations all wait for each other, leaving the loop bound by add latency rather than throughput. Give each half its own accumulator and process two vectors per iteration, for four accumulators in total, combined once the loop ends. Widened Int32 values sum exactly in Int64 and integer addition is associative, so the result is unchanged regardless of how the partial sums are grouped. Measured on arm64 with a 128-bit Vector: 2.0x at 1K elements, 2.4x at 8K and 2.6x at 1M, with no regression at 128 elements. --- .../System.Linq/src/System/Linq/Average.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/Average.cs b/src/libraries/System.Linq/src/System/Linq/Average.cs index 888a43c38ee214..2d99c7469d2eb2 100644 --- a/src/libraries/System.Linq/src/System/Linq/Average.cs +++ b/src/libraries/System.Linq/src/System/Linq/Average.cs @@ -31,16 +31,36 @@ public static double Average(this IEnumerable source) if (Vector.IsHardwareAccelerated && span.Length >= Vector.Count) { - Vector sums = default; - do + // Keep the halves of each widened vector, and two vectors per iteration, in + // separate accumulators: summing them into one makes every add wait for the + // previous one. Widened Int32 values sum exactly in Int64, in any order. + Vector sums0 = default; + Vector sums1 = default; + Vector sums2 = default; + Vector sums3 = default; + + while (i <= span.Length - (2 * Vector.Count)) { Vector.Widen(new Vector(span.Slice(i)), out Vector low, out Vector high); - sums += low; - sums += high; + Vector.Widen(new Vector(span.Slice(i + Vector.Count)), out Vector nextLow, out Vector nextHigh); + + sums0 += low; + sums1 += high; + sums2 += nextLow; + sums3 += nextHigh; + + i += 2 * Vector.Count; + } + + while (i <= span.Length - Vector.Count) + { + Vector.Widen(new Vector(span.Slice(i)), out Vector low, out Vector high); + sums0 += low; + sums1 += high; i += Vector.Count; } - while (i <= span.Length - Vector.Count); - sum += Vector.Sum(sums); + + sum += Vector.Sum((sums0 + sums1) + (sums2 + sums3)); } for (; (uint)i < (uint)span.Length; i++)