From 5c3670f598a17567f04328fd82e409c59f9c641e Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Sat, 26 Apr 2025 12:56:08 +0100 Subject: [PATCH 1/8] Specific overloads (float, float32, int, int64) of Seq.sum, Seq.average, Array.sum and Array.average to take advantage of vectorization in System.Linq.Enumerable module. --- src/FSharp.Core/array.fs | 24 +++++++ src/FSharp.Core/array.fsi | 126 ++++++++++++++++++++++++++++++++++++ src/FSharp.Core/seq.fs | 24 +++++++ src/FSharp.Core/seq.fsi | 132 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 306 insertions(+) diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs index ca88e22465d..cd9402784ab 100644 --- a/src/FSharp.Core/array.fs +++ b/src/FSharp.Core/array.fs @@ -1588,6 +1588,22 @@ module Array = acc + [] + let inline sumFloat (array: float array) : float = + System.Linq.Enumerable.Sum array + + [] + let inline sumFloat32 (array: float32 array) : float32 = + System.Linq.Enumerable.Sum array + + [] + let inline sumInt (array: int array) : int = + System.Linq.Enumerable.Sum array + + [] + let inline sumInt64 (array: int64 array) : int64 = + System.Linq.Enumerable.Sum array + [] let inline sumBy ([] projection: 'T -> ^U) (array: 'T array) : ^U = checkNonNull "array" array @@ -1686,6 +1702,14 @@ module Array = LanguagePrimitives.DivideByInt< ^T> acc array.Length + [] + let inline averageFloat (array: float array) : float = + System.Linq.Enumerable.Average array + + [] + let inline averageFloat32 (array: float32 array) : float32 = + System.Linq.Enumerable.Average array + [] let inline averageBy ([] projection: 'T -> ^U) (array: 'T array) : ^U = checkNonNull "array" array diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi index 151081c300c..1d050d39b8d 100644 --- a/src/FSharp.Core/array.fsi +++ b/src/FSharp.Core/array.fsi @@ -84,6 +84,56 @@ module Array = and ^T: (static member DivideByInt: ^T * int -> ^T) and ^T: (static member Zero: ^T) + /// Returns the average of the elements in the array using vectorization. + /// + /// The input array. + /// + /// Thrown when array is empty. + /// Thrown when the input array is null. + /// + /// The average of the elements in the array. + /// + /// + /// + /// [| 1.0; 2.0; 6.0 |] |> Array.average + /// + /// Evaluates to 3.0 + /// + /// + /// + /// + /// [| |] |> Array.average + /// + /// Throws ArgumentException + /// + [] + val inline averageFloat: array: float32 array -> float32 + + /// Returns the average of the elements in the array using vectorization. + /// + /// The input array. + /// + /// Thrown when array is empty. + /// Thrown when the input array is null. + /// + /// The average of the elements in the array. + /// + /// + /// + /// [| 1f; 2f; 6f |] |> Array.average + /// + /// Evaluates to 3f + /// + /// + /// + /// + /// [| |] |> Array.average + /// + /// Throws ArgumentException + /// + [] + val inline averageFloat32: array: float32 array -> float32 + /// Returns the average of the elements generated by applying the function to each element of the array. /// /// The function to transform the array elements before averaging. @@ -2484,6 +2534,82 @@ module Array = [] val inline sum: array: ^T array -> ^T when ^T: (static member (+): ^T * ^T -> ^T) and ^T: (static member Zero: ^T) + /// Returns the sum of the elements in the array using vectorization. + /// + /// The input array. + /// + /// The resulting sum. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let input = [| 1.; 5.; 3.; 2. |] + /// + /// input |> Array.sum + /// + /// Evaluates to 11.. + /// + [] + val inline sumFloat: array: float array -> float + + /// Returns the sum of the elements in the array using vectorization. + /// + /// The input array. + /// + /// The resulting sum. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let input = [| 1f; 5f; 3f; 2f |] + /// + /// input |> Array.sum + /// + /// Evaluates to 11f. + /// + [] + val inline sumFloat32: array: float32 array -> float32 + + /// Returns the sum of the elements in the array using vectorization. + /// + /// The input array. + /// + /// The resulting sum. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let input = [| 1; 5; 3; 2 |] + /// + /// input |> Array.sum + /// + /// Evaluates to 11. + /// + [] + val inline sumInt: array: int array -> int + + /// Returns the sum of the elements in the array using vectorization. + /// + /// The input array. + /// + /// The resulting sum. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let input = [| 1L; 5L; 3L; 2L |] + /// + /// input |> Array.sum + /// + /// Evaluates to 11L. + /// + [] + val inline sumInt64: array: int64 array -> int64 + /// Returns the sum of the results generated by applying the function to each element of the array. /// /// The function to transform the array elements into the type to be summed. diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index 20fcdefb159..70718864522 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -1473,6 +1473,22 @@ module Seq = acc + [] + let inline sumFloat (array: float array) : float = + System.Linq.Enumerable.Sum array + + [] + let inline sumFloat32 (array: float32 array) : float32 = + System.Linq.Enumerable.Sum array + + [] + let inline sumInt (array: int array) : int = + System.Linq.Enumerable.Sum array + + [] + let inline sumInt64 (array: int64 array) : int64 = + System.Linq.Enumerable.Sum array + [] let inline sumBy ([] projection: 'T -> ^U) (source: seq<'T>) : ^U = use e = source.GetEnumerator() @@ -1499,6 +1515,14 @@ module Seq = LanguagePrimitives.DivideByInt< ^a> acc count + [] + let inline averageFloat (source: seq) : float = + System.Linq.Enumerable.Average source + + [] + let inline averageFloat32 (source: seq) : float32 = + System.Linq.Enumerable.Average source + [] let inline averageBy ([] projection: 'T -> ^U) (source: seq<'T>) : ^U = checkNonNull "source" source diff --git a/src/FSharp.Core/seq.fsi b/src/FSharp.Core/seq.fsi index 790a486e850..ced7bc5bd00 100644 --- a/src/FSharp.Core/seq.fsi +++ b/src/FSharp.Core/seq.fsi @@ -89,6 +89,62 @@ module Seq = and ^T: (static member DivideByInt: ^T * int -> ^T) and ^T: (static member Zero: ^T) + /// Returns the average of the elements in the sequence using vectorization. + /// + /// The elements are averaged using the + operator, DivideByInt method and Zero property + /// associated with the element type. + /// + /// The input sequence. + /// + /// The average. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence has zero elements. + /// + /// + /// + /// [1.0; 2.0; 3.0] |> Seq.average + /// + /// Evaluates to 2.0 + /// + /// + /// + /// + /// [] |> Seq.average + /// + /// Throws ArgumentException + /// + [] + val inline averageFloat: source: seq -> float + + /// Returns the average of the elements in the sequence using vectorization. + /// + /// The elements are averaged using the + operator, DivideByInt method and Zero property + /// associated with the element type. + /// + /// The input sequence. + /// + /// The average. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence has zero elements. + /// + /// + /// + /// [1f; 2f; 3f] |> Seq.average + /// + /// Evaluates to 2f + /// + /// + /// + /// + /// [] |> Seq.average + /// + /// Throws ArgumentException + /// + [] + val inline averageFloat32: source: seq -> float32 + /// Returns the average of the results generated by applying the function to each element /// of the sequence. /// @@ -2347,6 +2403,82 @@ module Seq = [] val inline sum: source: seq<(^T)> -> ^T when ^T: (static member (+): ^T * ^T -> ^T) and ^T: (static member Zero: ^T) + /// Returns the sum of the elements in the sequence using vectorization. + /// + /// The elements are summed using the + operator and Zero property associated with the generated type. + /// + /// The input sequence. + /// + /// The computed sum. + /// + /// + /// + /// let input = [ 1.; 5.; 3.; 2. ] + /// + /// input |> Seq.sum + /// + /// Evaluates to 11.. + /// + [] + val inline sumFloat: source: seq -> float + + /// Returns the sum of the elements in the sequence using vectorization. + /// + /// The elements are summed using the + operator and Zero property associated with the generated type. + /// + /// The input sequence. + /// + /// The computed sum. + /// + /// + /// + /// let input = [ 1f; 5f; 3f; 2f ] + /// + /// input |> Seq.sum + /// + /// Evaluates to 11f. + /// + [] + val inline sumFloat32: source: seq -> float32 + + /// Returns the sum of the elements in the sequence using vectorization. + /// + /// The elements are summed using the + operator and Zero property associated with the generated type. + /// + /// The input sequence. + /// + /// The computed sum. + /// + /// + /// + /// let input = [ 1; 5; 3; 2 ] + /// + /// input |> Seq.sum + /// + /// Evaluates to 11. + /// + [] + val inline sumInt: source: seq -> int + + /// Returns the sum of the elements in the sequence using vectorization. + /// + /// The elements are summed using the + operator and Zero property associated with the generated type. + /// + /// The input sequence. + /// + /// The computed sum. + /// + /// + /// + /// let input = [ 1; 5; 3; 2 ] + /// + /// input |> Seq.sum + /// + /// Evaluates to 11. + /// + [] + val inline sumInt64: source: seq -> int64 + /// Returns the sum of the results generated by applying the function to each element of the sequence. /// /// The generated elements are summed using the + operator and Zero property associated with the generated type. From e924815e407c00e0b6ef717a5177008146e7d583 Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Sat, 26 Apr 2025 19:44:23 +0100 Subject: [PATCH 2/8] Changed to static optimization conditionals --- src/FSharp.Core/array.fs | 30 ++------- src/FSharp.Core/array.fsi | 126 ------------------------------------ src/FSharp.Core/seq.fs | 37 +++-------- src/FSharp.Core/seq.fsi | 132 -------------------------------------- 4 files changed, 16 insertions(+), 309 deletions(-) diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs index cd9402784ab..b88cf551e10 100644 --- a/src/FSharp.Core/array.fs +++ b/src/FSharp.Core/array.fs @@ -1587,22 +1587,10 @@ module Array = acc <- Checked.(+) acc array.[i] acc - - [] - let inline sumFloat (array: float array) : float = - System.Linq.Enumerable.Sum array - - [] - let inline sumFloat32 (array: float32 array) : float32 = - System.Linq.Enumerable.Sum array - - [] - let inline sumInt (array: int array) : int = - System.Linq.Enumerable.Sum array - - [] - let inline sumInt64 (array: int64 array) : int64 = - System.Linq.Enumerable.Sum array + when ^T : float = (System.Linq.Enumerable.Sum : IEnumerable -> float) (# "" array : IEnumerable #) + when ^T : float32 = (System.Linq.Enumerable.Sum : IEnumerable -> float32) (# "" array : IEnumerable #) + when ^T : int = (System.Linq.Enumerable.Sum : IEnumerable -> int) (# "" array : IEnumerable #) + when ^T : int64 = (System.Linq.Enumerable.Sum : IEnumerable -> int64) (# "" array : IEnumerable #) [] let inline sumBy ([] projection: 'T -> ^U) (array: 'T array) : ^U = @@ -1701,14 +1689,8 @@ module Array = acc <- Checked.(+) acc array.[i] LanguagePrimitives.DivideByInt< ^T> acc array.Length - - [] - let inline averageFloat (array: float array) : float = - System.Linq.Enumerable.Average array - - [] - let inline averageFloat32 (array: float32 array) : float32 = - System.Linq.Enumerable.Average array + when ^T : float = (System.Linq.Enumerable.Average : IEnumerable -> float) (# "" array : IEnumerable #) + when ^T : float32 = (System.Linq.Enumerable.Average : IEnumerable -> float32) (# "" array : IEnumerable #) [] let inline averageBy ([] projection: 'T -> ^U) (array: 'T array) : ^U = diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi index 1d050d39b8d..151081c300c 100644 --- a/src/FSharp.Core/array.fsi +++ b/src/FSharp.Core/array.fsi @@ -84,56 +84,6 @@ module Array = and ^T: (static member DivideByInt: ^T * int -> ^T) and ^T: (static member Zero: ^T) - /// Returns the average of the elements in the array using vectorization. - /// - /// The input array. - /// - /// Thrown when array is empty. - /// Thrown when the input array is null. - /// - /// The average of the elements in the array. - /// - /// - /// - /// [| 1.0; 2.0; 6.0 |] |> Array.average - /// - /// Evaluates to 3.0 - /// - /// - /// - /// - /// [| |] |> Array.average - /// - /// Throws ArgumentException - /// - [] - val inline averageFloat: array: float32 array -> float32 - - /// Returns the average of the elements in the array using vectorization. - /// - /// The input array. - /// - /// Thrown when array is empty. - /// Thrown when the input array is null. - /// - /// The average of the elements in the array. - /// - /// - /// - /// [| 1f; 2f; 6f |] |> Array.average - /// - /// Evaluates to 3f - /// - /// - /// - /// - /// [| |] |> Array.average - /// - /// Throws ArgumentException - /// - [] - val inline averageFloat32: array: float32 array -> float32 - /// Returns the average of the elements generated by applying the function to each element of the array. /// /// The function to transform the array elements before averaging. @@ -2534,82 +2484,6 @@ module Array = [] val inline sum: array: ^T array -> ^T when ^T: (static member (+): ^T * ^T -> ^T) and ^T: (static member Zero: ^T) - /// Returns the sum of the elements in the array using vectorization. - /// - /// The input array. - /// - /// The resulting sum. - /// - /// Thrown when the input array is null. - /// - /// - /// - /// let input = [| 1.; 5.; 3.; 2. |] - /// - /// input |> Array.sum - /// - /// Evaluates to 11.. - /// - [] - val inline sumFloat: array: float array -> float - - /// Returns the sum of the elements in the array using vectorization. - /// - /// The input array. - /// - /// The resulting sum. - /// - /// Thrown when the input array is null. - /// - /// - /// - /// let input = [| 1f; 5f; 3f; 2f |] - /// - /// input |> Array.sum - /// - /// Evaluates to 11f. - /// - [] - val inline sumFloat32: array: float32 array -> float32 - - /// Returns the sum of the elements in the array using vectorization. - /// - /// The input array. - /// - /// The resulting sum. - /// - /// Thrown when the input array is null. - /// - /// - /// - /// let input = [| 1; 5; 3; 2 |] - /// - /// input |> Array.sum - /// - /// Evaluates to 11. - /// - [] - val inline sumInt: array: int array -> int - - /// Returns the sum of the elements in the array using vectorization. - /// - /// The input array. - /// - /// The resulting sum. - /// - /// Thrown when the input array is null. - /// - /// - /// - /// let input = [| 1L; 5L; 3L; 2L |] - /// - /// input |> Array.sum - /// - /// Evaluates to 11L. - /// - [] - val inline sumInt64: array: int64 array -> int64 - /// Returns the sum of the results generated by applying the function to each element of the array. /// /// The function to transform the array elements into the type to be summed. diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index 70718864522..ea5bc004a60 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -478,7 +478,8 @@ module Internal = static member Bind(g: Generator<'T>, cont) = match g with | :? GenerateThen<'T> as g -> - GenerateThen<_>.Bind(g.Generator, (fun () -> GenerateThen<_>.Bind(g.Cont(), cont))) + GenerateThen<_> + .Bind(g.Generator, (fun () -> GenerateThen<_>.Bind(g.Cont(), cont))) | g -> (new GenerateThen<'T>(g, cont) :> Generator<'T>) let bindG g cont = @@ -1471,23 +1472,11 @@ module Seq = while e.MoveNext() do acc <- Checked.(+) acc e.Current - acc - - [] - let inline sumFloat (array: float array) : float = - System.Linq.Enumerable.Sum array - - [] - let inline sumFloat32 (array: float32 array) : float32 = - System.Linq.Enumerable.Sum array - - [] - let inline sumInt (array: int array) : int = - System.Linq.Enumerable.Sum array - - [] - let inline sumInt64 (array: int64 array) : int64 = - System.Linq.Enumerable.Sum array + acc + when ^a: int64 = (System.Linq.Enumerable.Sum: IEnumerable -> int64) (# "" source : IEnumerable #) + when ^a: int = (System.Linq.Enumerable.Sum: IEnumerable -> int) (# "" source : IEnumerable #) + when ^a: float32 = (System.Linq.Enumerable.Sum: IEnumerable -> float32) (# "" source : IEnumerable #) + when ^a: float = (System.Linq.Enumerable.Sum: IEnumerable -> float) (# "" source : IEnumerable #) [] let inline sumBy ([] projection: 'T -> ^U) (source: seq<'T>) : ^U = @@ -1513,15 +1502,9 @@ module Seq = if count = 0 then invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString - LanguagePrimitives.DivideByInt< ^a> acc count - - [] - let inline averageFloat (source: seq) : float = - System.Linq.Enumerable.Average source - - [] - let inline averageFloat32 (source: seq) : float32 = - System.Linq.Enumerable.Average source + LanguagePrimitives.DivideByInt< ^a> acc count + when ^a: float32 = (System.Linq.Enumerable.Average: IEnumerable -> float32) (# "" source : IEnumerable #) + when ^a: float = (System.Linq.Enumerable.Average: IEnumerable -> float) (# "" source : IEnumerable #) [] let inline averageBy ([] projection: 'T -> ^U) (source: seq<'T>) : ^U = diff --git a/src/FSharp.Core/seq.fsi b/src/FSharp.Core/seq.fsi index ced7bc5bd00..790a486e850 100644 --- a/src/FSharp.Core/seq.fsi +++ b/src/FSharp.Core/seq.fsi @@ -89,62 +89,6 @@ module Seq = and ^T: (static member DivideByInt: ^T * int -> ^T) and ^T: (static member Zero: ^T) - /// Returns the average of the elements in the sequence using vectorization. - /// - /// The elements are averaged using the + operator, DivideByInt method and Zero property - /// associated with the element type. - /// - /// The input sequence. - /// - /// The average. - /// - /// Thrown when the input sequence is null. - /// Thrown when the input sequence has zero elements. - /// - /// - /// - /// [1.0; 2.0; 3.0] |> Seq.average - /// - /// Evaluates to 2.0 - /// - /// - /// - /// - /// [] |> Seq.average - /// - /// Throws ArgumentException - /// - [] - val inline averageFloat: source: seq -> float - - /// Returns the average of the elements in the sequence using vectorization. - /// - /// The elements are averaged using the + operator, DivideByInt method and Zero property - /// associated with the element type. - /// - /// The input sequence. - /// - /// The average. - /// - /// Thrown when the input sequence is null. - /// Thrown when the input sequence has zero elements. - /// - /// - /// - /// [1f; 2f; 3f] |> Seq.average - /// - /// Evaluates to 2f - /// - /// - /// - /// - /// [] |> Seq.average - /// - /// Throws ArgumentException - /// - [] - val inline averageFloat32: source: seq -> float32 - /// Returns the average of the results generated by applying the function to each element /// of the sequence. /// @@ -2403,82 +2347,6 @@ module Seq = [] val inline sum: source: seq<(^T)> -> ^T when ^T: (static member (+): ^T * ^T -> ^T) and ^T: (static member Zero: ^T) - /// Returns the sum of the elements in the sequence using vectorization. - /// - /// The elements are summed using the + operator and Zero property associated with the generated type. - /// - /// The input sequence. - /// - /// The computed sum. - /// - /// - /// - /// let input = [ 1.; 5.; 3.; 2. ] - /// - /// input |> Seq.sum - /// - /// Evaluates to 11.. - /// - [] - val inline sumFloat: source: seq -> float - - /// Returns the sum of the elements in the sequence using vectorization. - /// - /// The elements are summed using the + operator and Zero property associated with the generated type. - /// - /// The input sequence. - /// - /// The computed sum. - /// - /// - /// - /// let input = [ 1f; 5f; 3f; 2f ] - /// - /// input |> Seq.sum - /// - /// Evaluates to 11f. - /// - [] - val inline sumFloat32: source: seq -> float32 - - /// Returns the sum of the elements in the sequence using vectorization. - /// - /// The elements are summed using the + operator and Zero property associated with the generated type. - /// - /// The input sequence. - /// - /// The computed sum. - /// - /// - /// - /// let input = [ 1; 5; 3; 2 ] - /// - /// input |> Seq.sum - /// - /// Evaluates to 11. - /// - [] - val inline sumInt: source: seq -> int - - /// Returns the sum of the elements in the sequence using vectorization. - /// - /// The elements are summed using the + operator and Zero property associated with the generated type. - /// - /// The input sequence. - /// - /// The computed sum. - /// - /// - /// - /// let input = [ 1; 5; 3; 2 ] - /// - /// input |> Seq.sum - /// - /// Evaluates to 11. - /// - [] - val inline sumInt64: source: seq -> int64 - /// Returns the sum of the results generated by applying the function to each element of the sequence. /// /// The generated elements are summed using the + operator and Zero property associated with the generated type. From 61ec211435dfca8ea0823f0d5ebdeb1a7c8fd332 Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Sat, 26 Apr 2025 22:24:27 +0100 Subject: [PATCH 3/8] Release notes update, one benchmark added --- docs/release-notes/.FSharp.Core/9.0.300.md | 1 + .../CompiledCodeBenchmarks/MicroPerf/Collections.fs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/release-notes/.FSharp.Core/9.0.300.md b/docs/release-notes/.FSharp.Core/9.0.300.md index d730ff12486..c74f6bbf565 100644 --- a/docs/release-notes/.FSharp.Core/9.0.300.md +++ b/docs/release-notes/.FSharp.Core/9.0.300.md @@ -6,6 +6,7 @@ * Support for `and!` in `TaskBuilder` ([LanguageSuggestion #1363](https://github.com/fsharp/fslang-suggestions/issues/1363), [PR #18451](https://github.com/dotnet/fsharp/pull/18451)) ### Changed +* Array.sum, Array.average, Seq.sum and Seq.average to call System.Linq.Enumerable methods on base-types (float/float32/int/int64) to utilize vectorization. [PR #18509](https://github.com/dotnet/fsharp/pull/18509) ### Breaking Changes * Struct unions with overlapping fields now generate mappings needed for reading via reflection ([Issue #18121](https://github.com/dotnet/fsharp/issues/17797), [PR #18274](https://github.com/dotnet/fsharp/pull/18274)). Previous versions of FSharp.Core returned incomplete mapping between fields and cases, these older fslib versions will now report an exception. diff --git a/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/Collections.fs b/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/Collections.fs index 50f1719c5b5..9e1f84548b9 100644 --- a/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/Collections.fs +++ b/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/Collections.fs @@ -141,6 +141,11 @@ type CollectionsBenchmark() = |> Array.updateAt (x.Length - 1) 1 |> ignore + [] + member x.ArraySum() = + array + |> Array.sum + |> ignore /// Seq [] member x.SeqBaseline() = From 6f17f6528620e4d046719b92e5a85356f0a30b9e Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Mon, 28 Apr 2025 09:57:26 +0100 Subject: [PATCH 4/8] Reverted Average because it didn't perform better when tested with benchmarks\CompiledCodeBenchmarks\MicroPerf\MicroPerf.fsproj --- docs/release-notes/.FSharp.Core/9.0.300.md | 2 +- src/FSharp.Core/array.fs | 2 -- src/FSharp.Core/seq.fs | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/release-notes/.FSharp.Core/9.0.300.md b/docs/release-notes/.FSharp.Core/9.0.300.md index c74f6bbf565..00d75b89748 100644 --- a/docs/release-notes/.FSharp.Core/9.0.300.md +++ b/docs/release-notes/.FSharp.Core/9.0.300.md @@ -6,7 +6,7 @@ * Support for `and!` in `TaskBuilder` ([LanguageSuggestion #1363](https://github.com/fsharp/fslang-suggestions/issues/1363), [PR #18451](https://github.com/dotnet/fsharp/pull/18451)) ### Changed -* Array.sum, Array.average, Seq.sum and Seq.average to call System.Linq.Enumerable methods on base-types (float/float32/int/int64) to utilize vectorization. [PR #18509](https://github.com/dotnet/fsharp/pull/18509) +* Array.sum and Seq.sum to call System.Linq.Enumerable methods on base-types (float/float32/int/int64) to utilize vectorization. [PR #18509](https://github.com/dotnet/fsharp/pull/18509) ### Breaking Changes * Struct unions with overlapping fields now generate mappings needed for reading via reflection ([Issue #18121](https://github.com/dotnet/fsharp/issues/17797), [PR #18274](https://github.com/dotnet/fsharp/pull/18274)). Previous versions of FSharp.Core returned incomplete mapping between fields and cases, these older fslib versions will now report an exception. diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs index b88cf551e10..f78f6c7c19c 100644 --- a/src/FSharp.Core/array.fs +++ b/src/FSharp.Core/array.fs @@ -1689,8 +1689,6 @@ module Array = acc <- Checked.(+) acc array.[i] LanguagePrimitives.DivideByInt< ^T> acc array.Length - when ^T : float = (System.Linq.Enumerable.Average : IEnumerable -> float) (# "" array : IEnumerable #) - when ^T : float32 = (System.Linq.Enumerable.Average : IEnumerable -> float32) (# "" array : IEnumerable #) [] let inline averageBy ([] projection: 'T -> ^U) (array: 'T array) : ^U = diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index ea5bc004a60..3421921285c 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -1503,8 +1503,6 @@ module Seq = invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString LanguagePrimitives.DivideByInt< ^a> acc count - when ^a: float32 = (System.Linq.Enumerable.Average: IEnumerable -> float32) (# "" source : IEnumerable #) - when ^a: float = (System.Linq.Enumerable.Average: IEnumerable -> float) (# "" source : IEnumerable #) [] let inline averageBy ([] projection: 'T -> ^U) (source: seq<'T>) : ^U = From 70fef772b62c50a62eed09d34246d976ae591669 Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Mon, 28 Apr 2025 10:30:07 +0100 Subject: [PATCH 5/8] Just clean up the extra space --- src/FSharp.Core/seq.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index 3421921285c..ca39fff8c8a 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -1502,7 +1502,7 @@ module Seq = if count = 0 then invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString - LanguagePrimitives.DivideByInt< ^a> acc count + LanguagePrimitives.DivideByInt< ^a> acc count [] let inline averageBy ([] projection: 'T -> ^U) (source: seq<'T>) : ^U = From 330ea57775ccb20a1f4687289f33d5db6b125936 Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Mon, 28 Apr 2025 14:23:16 +0100 Subject: [PATCH 6/8] Runtime check added to avoid .NET Framework change. --- src/FSharp.Core/array.fs | 31 +++++++++++++++++++++++++------ src/FSharp.Core/seq.fs | 31 +++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs index f78f6c7c19c..522b1f0d536 100644 --- a/src/FSharp.Core/array.fs +++ b/src/FSharp.Core/array.fs @@ -1578,8 +1578,7 @@ module Array = checkNonNull "array" array Microsoft.FSharp.Primitives.Basics.Array.permute indexMap array - [] - let inline sum (array: ^T array) : ^T = + let inline private classicSum (array: ^T array) : ^T = checkNonNull "array" array let mutable acc = LanguagePrimitives.GenericZero< ^T> @@ -1587,10 +1586,30 @@ module Array = acc <- Checked.(+) acc array.[i] acc - when ^T : float = (System.Linq.Enumerable.Sum : IEnumerable -> float) (# "" array : IEnumerable #) - when ^T : float32 = (System.Linq.Enumerable.Sum : IEnumerable -> float32) (# "" array : IEnumerable #) - when ^T : int = (System.Linq.Enumerable.Sum : IEnumerable -> int) (# "" array : IEnumerable #) - when ^T : int64 = (System.Linq.Enumerable.Sum : IEnumerable -> int64) (# "" array : IEnumerable #) + + [] + let inline sum (array: ^T array) : ^T = + classicSum array + when ^T : float = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + else + let r = (System.Linq.Enumerable.Sum : IEnumerable -> float) (# "" array : IEnumerable #) + (# "" r : 'T #) + when ^T : float32 = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + else + let r = (System.Linq.Enumerable.Sum : IEnumerable -> float32) (# "" array : IEnumerable #) + (# "" r : 'T #) + when ^T : int = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + else + let r = (System.Linq.Enumerable.Sum : IEnumerable -> int) (# "" array : IEnumerable #) + (# "" r : 'T #) + when ^T : int64 = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + else + let r = (System.Linq.Enumerable.Sum : IEnumerable -> int64) (# "" array : IEnumerable #) + (# "" r : 'T #) [] let inline sumBy ([] projection: 'T -> ^U) (array: 'T array) : ^U = diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index ca39fff8c8a..cbcc44a124a 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -1464,8 +1464,7 @@ module Seq = else mkDelayedSeq (fun () -> countByRefType projection source) - [] - let inline sum (source: seq< ^a >) : ^a = + let inline private classicSum (source: seq< ^a >) : ^a = use e = source.GetEnumerator() let mutable acc = LanguagePrimitives.GenericZero< ^a> @@ -1473,10 +1472,30 @@ module Seq = acc <- Checked.(+) acc e.Current acc - when ^a: int64 = (System.Linq.Enumerable.Sum: IEnumerable -> int64) (# "" source : IEnumerable #) - when ^a: int = (System.Linq.Enumerable.Sum: IEnumerable -> int) (# "" source : IEnumerable #) - when ^a: float32 = (System.Linq.Enumerable.Sum: IEnumerable -> float32) (# "" source : IEnumerable #) - when ^a: float = (System.Linq.Enumerable.Sum: IEnumerable -> float) (# "" source : IEnumerable #) + + [] + let inline sum (source: seq< ^a >) : ^a = + classicSum source + when ^a: int64 = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + else + let r = (System.Linq.Enumerable.Sum: IEnumerable -> int64) (# "" source : IEnumerable #) + (# "" r : 'a #) + when ^a: int = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + else + let r = (System.Linq.Enumerable.Sum: IEnumerable -> int) (# "" source : IEnumerable #) + (# "" r : 'a #) + when ^a: float32 = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + else + let r = (System.Linq.Enumerable.Sum: IEnumerable -> float32) (# "" source : IEnumerable #) + (# "" r : 'a #) + when ^a: float = + if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + else + let r = (System.Linq.Enumerable.Sum: IEnumerable -> float) (# "" source : IEnumerable #) + (# "" r : 'a #) [] let inline sumBy ([] projection: 'T -> ^U) (source: seq<'T>) : ^U = From 4c6265001ba749b750424a02fd80ba1b8980c364 Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Mon, 28 Apr 2025 18:07:53 +0100 Subject: [PATCH 7/8] Changed based on code-review --- src/FSharp.Core/array.fs | 14 ++++++++------ src/FSharp.Core/array.fsi | 5 +++++ src/FSharp.Core/seq.fs | 14 ++++++++------ src/FSharp.Core/seq.fsi | 5 +++++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs index 522b1f0d536..cd79d933ee9 100644 --- a/src/FSharp.Core/array.fs +++ b/src/FSharp.Core/array.fs @@ -1578,7 +1578,7 @@ module Array = checkNonNull "array" array Microsoft.FSharp.Primitives.Basics.Array.permute indexMap array - let inline private classicSum (array: ^T array) : ^T = + let inline private fsharpSumImpl (array: ^T array) : ^T = checkNonNull "array" array let mutable acc = LanguagePrimitives.GenericZero< ^T> @@ -1587,26 +1587,28 @@ module Array = acc + let isNetFramework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" + [] let inline sum (array: ^T array) : ^T = - classicSum array + fsharpSumImpl array when ^T : float = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + if isNetFramework then fsharpSumImpl array else let r = (System.Linq.Enumerable.Sum : IEnumerable -> float) (# "" array : IEnumerable #) (# "" r : 'T #) when ^T : float32 = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + if isNetFramework then fsharpSumImpl array else let r = (System.Linq.Enumerable.Sum : IEnumerable -> float32) (# "" array : IEnumerable #) (# "" r : 'T #) when ^T : int = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + if isNetFramework then fsharpSumImpl array else let r = (System.Linq.Enumerable.Sum : IEnumerable -> int) (# "" array : IEnumerable #) (# "" r : 'T #) when ^T : int64 = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum array + if isNetFramework then fsharpSumImpl array else let r = (System.Linq.Enumerable.Sum : IEnumerable -> int64) (# "" array : IEnumerable #) (# "" r : 'T #) diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi index 151081c300c..7675f7a86cf 100644 --- a/src/FSharp.Core/array.fsi +++ b/src/FSharp.Core/array.fsi @@ -2465,6 +2465,11 @@ module Array = [] val inline sortByDescending: projection: ('T -> 'Key) -> array: 'T array -> 'T array when 'Key: comparison + /// Internal use of Array.sum to detect if vectorization can be used. + /// Due to sum "inline" this can't be private. + [] + val isNetFramework : bool + /// Returns the sum of the elements in the array. /// /// The input array. diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index cbcc44a124a..c3d4fd7915e 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -1464,7 +1464,7 @@ module Seq = else mkDelayedSeq (fun () -> countByRefType projection source) - let inline private classicSum (source: seq< ^a >) : ^a = + let inline private fsharpSumImpl (source: seq< ^a >) : ^a = use e = source.GetEnumerator() let mutable acc = LanguagePrimitives.GenericZero< ^a> @@ -1473,26 +1473,28 @@ module Seq = acc + let isNetFramework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" + [] let inline sum (source: seq< ^a >) : ^a = - classicSum source + fsharpSumImpl source when ^a: int64 = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + if isNetFramework then fsharpSumImpl source else let r = (System.Linq.Enumerable.Sum: IEnumerable -> int64) (# "" source : IEnumerable #) (# "" r : 'a #) when ^a: int = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + if isNetFramework then fsharpSumImpl source else let r = (System.Linq.Enumerable.Sum: IEnumerable -> int) (# "" source : IEnumerable #) (# "" r : 'a #) when ^a: float32 = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + if isNetFramework then fsharpSumImpl source else let r = (System.Linq.Enumerable.Sum: IEnumerable -> float32) (# "" source : IEnumerable #) (# "" r : 'a #) when ^a: float = - if System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framework" then classicSum source + if isNetFramework then fsharpSumImpl source else let r = (System.Linq.Enumerable.Sum: IEnumerable -> float) (# "" source : IEnumerable #) (# "" r : 'a #) diff --git a/src/FSharp.Core/seq.fsi b/src/FSharp.Core/seq.fsi index 790a486e850..ca0272d3064 100644 --- a/src/FSharp.Core/seq.fsi +++ b/src/FSharp.Core/seq.fsi @@ -2328,6 +2328,11 @@ module Seq = [] val inline sortByDescending: projection: ('T -> 'Key) -> source: seq<'T> -> seq<'T> when 'Key: comparison + /// Internal use of Seq.sum to detect if vectorization can be used. + /// Due to sum "inline" this can't be private. + [] + val isNetFramework : bool + /// Returns the sum of the elements in the sequence. /// /// The elements are summed using the + operator and Zero property associated with the generated type. From a6979ac366d866524f4db4502f86a46c0e66013a Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Fri, 6 Jun 2025 08:48:33 +0100 Subject: [PATCH 8/8] Moved release-notes change to new version --- docs/release-notes/.FSharp.Core/10.0.100.md | 1 + docs/release-notes/.FSharp.Core/9.0.300.md | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Core/10.0.100.md b/docs/release-notes/.FSharp.Core/10.0.100.md index 10ff967c125..643930ee30f 100644 --- a/docs/release-notes/.FSharp.Core/10.0.100.md +++ b/docs/release-notes/.FSharp.Core/10.0.100.md @@ -7,5 +7,6 @@ ### Changed * Random functions support for zero element chosen/sampled ([PR #18568](https://github.com/dotnet/fsharp/pull/18568)) +* Array.sum and Seq.sum to call System.Linq.Enumerable methods on base-types (float/float32/int/int64) to utilize vectorization. [PR #18509](https://github.com/dotnet/fsharp/pull/18509) ### Breaking Changes diff --git a/docs/release-notes/.FSharp.Core/9.0.300.md b/docs/release-notes/.FSharp.Core/9.0.300.md index 00d75b89748..d730ff12486 100644 --- a/docs/release-notes/.FSharp.Core/9.0.300.md +++ b/docs/release-notes/.FSharp.Core/9.0.300.md @@ -6,7 +6,6 @@ * Support for `and!` in `TaskBuilder` ([LanguageSuggestion #1363](https://github.com/fsharp/fslang-suggestions/issues/1363), [PR #18451](https://github.com/dotnet/fsharp/pull/18451)) ### Changed -* Array.sum and Seq.sum to call System.Linq.Enumerable methods on base-types (float/float32/int/int64) to utilize vectorization. [PR #18509](https://github.com/dotnet/fsharp/pull/18509) ### Breaking Changes * Struct unions with overlapping fields now generate mappings needed for reading via reflection ([Issue #18121](https://github.com/dotnet/fsharp/issues/17797), [PR #18274](https://github.com/dotnet/fsharp/pull/18274)). Previous versions of FSharp.Core returned incomplete mapping between fields and cases, these older fslib versions will now report an exception.