From 563a0b908c8d12b4ec3760c43630c5bea125d20e Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 27 Feb 2023 17:49:16 +0100
Subject: [PATCH 01/13] Parallel sorting impl

---
 src/FSharp.Core/array.fs  | 155 ++++++++++++++++++++++++++++++++
 src/FSharp.Core/array.fsi | 180 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 335 insertions(+)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index 8c2fa8470fa..369b5055faa 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2072,3 +2072,158 @@ module Array =
                     iFalse <- iFalse + 1
 
             res1, res2
+
+        
+        // The following two parameters were benchmarked and found to be optimal.
+        // Benchmark was run using: 11th Gen Intel Core i9-11950H 2.60GHz, 1 CPU, 16 logical and 8 physical cores
+        let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use
+        let private sequentialCutoffForSorting = 2_500  // Arrays smaller then this will be sorted sequentially
+        let private minChunkSize = 64 // The minimum size of a chunk to be sorted in parallel
+     
+        let private createPartitions (array : 'T[]) = 
+            [|
+                let chunkSize = 
+                    match array.Length with 
+                    | smallSize when smallSize < minChunkSize -> smallSize
+                    | biggerSize when biggerSize % maxPartitions = 0 -> biggerSize / maxPartitions
+                    | biggerSize -> (biggerSize / maxPartitions) + 1            
+         
+                let mutable offset = 0
+
+                while (offset+chunkSize) <= array.Length do       
+                    yield new ArraySegment<'T>(array, offset, chunkSize)
+                    offset <- offset + chunkSize
+
+                if (offset <> array.Length) then
+                    yield new ArraySegment<'T>(array, offset, array.Length - offset)                      
+            |]
+
+        let private prepareSortedRunsInPlaceWith array comparer = 
+            let partitions = createPartitions array
+            Parallel.For(0,partitions.Length,fun i ->  
+                Array.Sort(array,partitions[i].Offset,partitions[i].Count,comparer)              
+                ) |> ignore
+
+            partitions
+
+        let private prepareSortedRunsInPlace array keysArray = 
+            let partitions = createPartitions array
+            let keyComparer = LanguagePrimitives.FastGenericComparerCanBeNull
+            Parallel.For(0,partitions.Length,fun i ->  
+                Array.Sort<_,_>(keysArray, array,partitions[i].Offset,partitions[i].Count, keyComparer) 
+                ) |> ignore
+
+            partitions
+
+        let inline swap leftIdx rightIdx (array:'T[]) = 
+            let temp = array[leftIdx]
+            array[leftIdx] <- array[rightIdx]
+            array[rightIdx] <- temp
+
+        let private mergeTwoSortedConsequtiveSegmentsInPlaceByKeys (keysArray:'TKey[]) (left:ArraySegment<'T>) (right: ArraySegment<'T>)  =                   
+            let mutable leftIdx = left.Offset       
+            let leftMax,rightMax = left.Offset+left.Count, right.Offset+right.Count
+
+            while leftIdx<leftMax  do
+                while (leftIdx<leftMax) && (compare keysArray[leftIdx] keysArray[right.Offset]) <= 0 do
+                    leftIdx <- leftIdx + 1
+
+                let leftMostUnprocessed = keysArray[leftIdx]        
+                let mutable writableRightIdx = right.Offset
+                while (writableRightIdx < rightMax) && (compare leftMostUnprocessed keysArray[writableRightIdx]) > 0 do   
+                    keysArray |> swap leftIdx writableRightIdx
+                    left.Array |> swap leftIdx writableRightIdx
+                    writableRightIdx <- writableRightIdx + 1
+                    leftIdx <- leftIdx + 1
+
+            new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
+
+        let private mergeTwoSortedConsequtiveSegmentsInPlaceWith (comparer:IComparer<'T>) (left:ArraySegment<'T>) (right: ArraySegment<'T>)  =        
+            let mutable leftIdx = left.Offset       
+            let leftMax,rightMax,fullArray = left.Offset+left.Count, right.Offset+right.Count, left.Array
+
+            while leftIdx<leftMax  do
+                while (leftIdx<leftMax) && comparer.Compare(fullArray[leftIdx],fullArray[right.Offset]) <= 0 do
+                    leftIdx <- leftIdx + 1
+
+                let leftMostUnprocessed = fullArray[leftIdx]        
+                let mutable writableRightIdx = right.Offset
+                while (writableRightIdx < rightMax) && comparer.Compare(leftMostUnprocessed,fullArray[writableRightIdx]) > 0 do                      
+                    fullArray |> swap leftIdx writableRightIdx
+                    writableRightIdx <- writableRightIdx + 1
+                    leftIdx <- leftIdx + 1
+
+            new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
+
+        let rec mergeRunsInParallel (segmentsInOrder:ArraySegment<'T> []) pairwiseMerger = 
+            match segmentsInOrder with
+            | [|singleRun|] ->  singleRun
+            | [|first;second|] -> pairwiseMerger first second
+            | [||] -> invalidArg "runs" LanguagePrimitives.ErrorStrings.InputArrayEmptyString
+            | threeOrMoreSegments ->   
+                let mutable left = None
+                let mutable right = None
+                let midIndex = threeOrMoreSegments.Length/2
+                Parallel.Invoke( 
+                    (fun () -> left <- Some (mergeRunsInParallel threeOrMoreSegments[0..midIndex-1] pairwiseMerger)),
+                    (fun () -> right <- Some (mergeRunsInParallel threeOrMoreSegments[midIndex..] pairwiseMerger)))
+                
+                pairwiseMerger left.Value right.Value
+
+        [<CompiledName("SortInPlaceWith")>]
+        let sortInPlaceWith comparer (array: 'T[]) =
+            checkNonNull "array" array
+            let comparer = ComparisonIdentity.FromFunction(comparer)  
+            if array.Length < sequentialCutoffForSorting then       
+                Array.Sort(array, comparer)
+            else
+                let preSortedPartitions = prepareSortedRunsInPlaceWith array comparer 
+                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceWith comparer) |> ignore
+
+        [<CompiledName("SortInPlaceBy")>]
+        let sortInPlaceBy (projection: 'T -> 'U) (array: 'T[]) =
+            checkNonNull "array" array
+            if array.Length < sequentialCutoffForSorting then
+                Microsoft.FSharp.Primitives.Basics.Array.unstableSortInPlaceBy projection array
+            else              
+                let projectedFields = map projection array
+                let preSortedPartitions = prepareSortedRunsInPlace array projectedFields            
+                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceByKeys projectedFields) |> ignore        
+            
+        [<CompiledName("SortInPlace")>]
+        let sortInPlace (array: 'T[]) =
+            checkNonNull "array" array          
+            if array.Length < sequentialCutoffForSorting then
+                Microsoft.FSharp.Primitives.Basics.Array.unstableSortInPlace array
+            else            
+                let preSortedPartitions = prepareSortedRunsInPlaceWith array LanguagePrimitives.FastGenericComparerCanBeNull
+                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceWith LanguagePrimitives.FastGenericComparer) |> ignore
+
+        [<CompiledName("SortWith")>]
+        let sortWith (comparer: 'T -> 'T -> int) (array: 'T[]) =           
+            let result = copy array
+            sortInPlaceWith comparer result
+            result
+
+        [<CompiledName("SortBy")>]
+        let sortBy projection array =         
+            let result = copy array
+            sortInPlaceBy projection result
+            result
+
+        [<CompiledName("Sort")>]
+        let sort array =        
+            let result = copy array
+            sortInPlace result
+            result
+
+        [<CompiledName("SortByDescending")>]
+        let inline sortByDescending projection array =
+            let inline compareDescending a b = compare (projection b) (projection a)            
+            sortWith compareDescending array
+
+        [<CompiledName("SortDescending")>]
+        let inline sortDescending array =   
+            let inline compareDescending a b = compare b a
+            sortWith compareDescending array
+         
diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi
index 2d08d1116e1..34d6a97ccfc 100644
--- a/src/FSharp.Core/array.fsi
+++ b/src/FSharp.Core/array.fsi
@@ -3307,3 +3307,183 @@ module Array =
         /// </example>
         [<CompiledName("Partition")>]
         val partition: predicate:('T -> bool) -> array:'T[] -> 'T[] * 'T[]
+
+        /// <summary>Sorts the elements of an array in parallel, returning a new array. Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>. </summary>
+        ///
+        /// <remarks>This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. 
+        /// For a stable sort, consider using <see cref="M:Microsoft.FSharp.Collections.SeqModule.Sort"/>.</remarks>
+        ///
+        /// <param name="array">The input array.</param>
+        ///
+        /// <returns>The sorted array.</returns>
+        ///
+        /// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
+        /// 
+        /// <example id="para-sort-1">
+        /// <code lang="fsharp">
+        /// let input = [| 8; 4; 3; 1; 6; 1 |]
+        ///
+        /// Array.Parallel.sort input
+        /// </code>
+        /// Evaluates to <c>[| 1; 1 3; 4; 6; 8 |]</c>.
+        /// </example>
+        [<CompiledName("Sort")>]
+        val sort: array:'T[] -> 'T[] when 'T : comparison 
+
+        /// <summary>Sorts the elements of an array in parallel, using the given projection for the keys and returning a new array. 
+        /// Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>.</summary>
+        ///
+        /// <remarks>This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. 
+        /// For a stable sort, consider using <see cref="M:Microsoft.FSharp.Collections.SeqModule.Sort"/>.</remarks>
+        ///
+        /// <param name="projection">The function to transform array elements into the type that is compared.</param>
+        /// <param name="array">The input array.</param>
+        ///
+        /// <returns>The sorted array.</returns>
+        ///
+        /// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
+        /// 
+        /// <example id="para-sortby-1">
+        /// <code lang="fsharp">
+        /// let input = [| "a"; "bbb"; "cccc"; "dd" |]
+        ///
+        /// input |> Array.Parallel.sortBy (fun s -> s.Length)
+        /// </code>
+        /// Evaluates to <c>[|"a"; "dd"; "bbb"; "cccc"|]</c>.
+        /// </example>
+
+        [<CompiledName("SortBy")>]
+        val sortBy: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison 
+
+        /// <summary>Sorts the elements of an array in parallel, using the given comparison function as the order, returning a new array.</summary>
+        ///
+        /// <remarks>This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. 
+        /// For a stable sort, consider using <see cref="M:Microsoft.FSharp.Collections.SeqModule.Sort"/>.</remarks>
+        ///
+        /// <param name="comparer">The function to compare pairs of array elements.</param>
+        /// <param name="array">The input array.</param>
+        ///
+        /// <returns>The sorted array.</returns>
+        ///
+        /// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
+        /// 
+        /// <example id="para-sortwith-1">Sort an array of pairs using a comparison function that compares string lengths then index numbers:
+        /// <code lang="fsharp">
+        /// let compareEntries (n1: int, s1: string) (n2: int, s2: string) =
+        ///     let c = compare s1.Length s2.Length
+        ///     if c &lt;> 0 then c else
+        ///     compare n1 n2
+        ///
+        /// let input = [| (0,"aa"); (1,"bbb"); (2,"cc"); (3,"dd") |]
+        ///
+        /// input |> Array.Parallel.sortWith compareEntries
+        /// </code>
+        /// Evaluates to <c>[|(0, "aa"); (2, "cc"); (3, "dd"); (1, "bbb")|]</c>.
+        /// </example>
+        [<CompiledName("SortWith")>]
+        val sortWith: comparer:('T -> 'T -> int) -> array:'T[] -> 'T[]
+
+        /// <summary>Sorts the elements of an array by mutating the array in-place in parallel, using the given projection for the keys. 
+        /// Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>.</summary>
+        ///
+        /// <remarks>This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. 
+        /// For a stable sort, consider using <see cref="M:Microsoft.FSharp.Collections.SeqModule.Sort"/>.</remarks>
+        ///
+        /// <param name="projection">The function to transform array elements into the type that is compared.</param>
+        /// <param name="array">The input array.</param>
+        ///
+        /// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
+        /// 
+        /// <example id="para-sortinplaceby-1">
+        /// <code lang="fsharp">
+        /// let array = [| "a"; "bbb"; "cccc"; "dd" |]
+        ///
+        /// array |> Array.Parallel.sortInPlaceBy (fun s -> s.Length)
+        /// </code>
+        /// After evaluation <c>array</c> contains <c>[|"a"; "dd"; "bbb"; "cccc"|]</c>.
+        /// </example>
+        [<CompiledName("SortInPlaceBy")>]
+        val sortInPlaceBy: projection:('T -> 'Key) -> array:'T[] -> unit when 'Key : comparison 
+
+        /// <summary>Sorts the elements of an array by mutating the array in-place in parallel, using the given comparison function as the order.</summary>
+        ///
+        /// <param name="comparer">The function to compare pairs of array elements.</param>
+        /// <param name="array">The input array.</param>
+        ///
+        /// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
+        /// 
+        /// <example id="para-sortinplacewith-1"> The following sorts entries using a comparison function that compares string lengths then index numbers:
+        /// <code lang="fsharp">
+        /// let compareEntries (n1: int, s1: string) (n2: int, s2: string) =
+        ///     let c = compare s1.Length s2.Length
+        ///     if c &lt;> 0 then c else
+        ///     compare n1 n2
+        ///
+        /// let array = [| (0,"aa"); (1,"bbb"); (2,"cc"); (3,"dd") |]
+        ///
+        /// array |> Array.Parallel.sortInPlaceWith compareEntries
+        /// </code>
+        /// After evaluation <c>array</c> contains <c>[|(0, "aa"); (2, "cc"); (3, "dd"); (1, "bbb")|]</c>.
+        /// </example>
+        [<CompiledName("SortInPlaceWith")>]
+        val sortInPlaceWith: comparer:('T -> 'T -> int) -> array:'T[] -> unit
+
+        /// <summary>Sorts the elements of an array by mutating the array in-place in parallel, using the given comparison function. 
+        /// Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>.</summary>
+        ///
+        /// <param name="array">The input array.</param>
+        ///
+        /// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
+        /// 
+        /// <example id="para-sortinplace-1">
+        /// <code lang="fsharp">
+        /// let array = [| 8; 4; 3; 1; 6; 1 |]
+        ///
+        /// Array.sortInPlace array
+        /// </code>
+        /// After evaluation <c>array</c> contains <c>[| 1; 1; 3; 4; 6; 8 |]</c>.
+        /// </example>
+        [<CompiledName("SortInPlace")>]
+        val sortInPlace: array:'T[] -> unit when 'T : comparison 
+
+        /// <summary>Sorts the elements of an array in parallel, in descending order, returning a new array. Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>. </summary>
+        ///
+        /// <remarks>This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. 
+        /// For a stable sort, consider using <see cref="M:Microsoft.FSharp.Collections.SeqModule.Sort"/>.</remarks>
+        ///
+        /// <param name="array">The input array.</param>
+        ///
+        /// <returns>The sorted array.</returns>
+        /// 
+        /// <example id="para-sortdescending-1">
+        /// <code lang="fsharp">
+        /// let input = [| 8; 4; 3; 1; 6; 1 |]
+        ///
+        /// input |> Array.Parallel.sortDescending
+        /// </code>
+        /// Evaluates to <c>[| 8; 6; 4; 3; 1; 1 |]</c>.
+        /// </example>
+        [<CompiledName("SortDescending")>]
+        val inline sortDescending: array:'T[] -> 'T[] when 'T : comparison
+
+        /// <summary>Sorts the elements of an array in parallel, in descending order, using the given projection for the keys and returning a new array. 
+        /// Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>.</summary>
+        ///
+        /// <remarks>This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. 
+        /// For a stable sort, consider using <see cref="M:Microsoft.FSharp.Collections.SeqModule.Sort"/>.</remarks>
+        ///
+        /// <param name="projection">The function to transform array elements into the type that is compared.</param>
+        /// <param name="array">The input array.</param>
+        ///
+        /// <returns>The sorted array.</returns>
+        /// 
+        /// <example id="para-sortbydescending-1">
+        /// <code lang="fsharp">
+        /// let input = [| "a"; "bbb"; "cccc"; "dd" |]
+        ///
+        /// input |> Array.Parallel.sortByDescending (fun s -> s.Length)
+        /// </code>
+        /// Evaluates to <c>[|"cccc"; "bbb"; "dd"; "a"|]</c>.
+        /// </example>
+        [<CompiledName("SortByDescending")>]
+        val inline sortByDescending: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison

From 971e425b571285dd2108e4e76511fe0d02134dc5 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 27 Feb 2023 20:29:21 +0100
Subject: [PATCH 02/13] fantomas applied

---
 src/FSharp.Core/array.fs | 161 ++++++++++++++++++++++++---------------
 1 file changed, 100 insertions(+), 61 deletions(-)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index 369b5055faa..e2f7b90951d 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2073,64 +2073,77 @@ module Array =
 
             res1, res2
 
-        
         // The following two parameters were benchmarked and found to be optimal.
         // Benchmark was run using: 11th Gen Intel Core i9-11950H 2.60GHz, 1 CPU, 16 logical and 8 physical cores
         let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use
-        let private sequentialCutoffForSorting = 2_500  // Arrays smaller then this will be sorted sequentially
+        let private sequentialCutoffForSorting = 2_500 // Arrays smaller then this will be sorted sequentially
         let private minChunkSize = 64 // The minimum size of a chunk to be sorted in parallel
-     
-        let private createPartitions (array : 'T[]) = 
+
+        let private createPartitions (array: 'T[]) =
             [|
-                let chunkSize = 
-                    match array.Length with 
+                let chunkSize =
+                    match array.Length with
                     | smallSize when smallSize < minChunkSize -> smallSize
                     | biggerSize when biggerSize % maxPartitions = 0 -> biggerSize / maxPartitions
-                    | biggerSize -> (biggerSize / maxPartitions) + 1            
-         
+                    | biggerSize -> (biggerSize / maxPartitions) + 1
+
                 let mutable offset = 0
 
-                while (offset+chunkSize) <= array.Length do       
+                while (offset + chunkSize) <= array.Length do
                     yield new ArraySegment<'T>(array, offset, chunkSize)
                     offset <- offset + chunkSize
 
                 if (offset <> array.Length) then
-                    yield new ArraySegment<'T>(array, offset, array.Length - offset)                      
+                    yield new ArraySegment<'T>(array, offset, array.Length - offset)
             |]
 
-        let private prepareSortedRunsInPlaceWith array comparer = 
+        let private prepareSortedRunsInPlaceWith array comparer =
             let partitions = createPartitions array
-            Parallel.For(0,partitions.Length,fun i ->  
-                Array.Sort(array,partitions[i].Offset,partitions[i].Count,comparer)              
-                ) |> ignore
+
+            Parallel.For(
+                0,
+                partitions.Length,
+                fun i -> Array.Sort(array, partitions[i].Offset, partitions[i].Count, comparer)
+            )
+            |> ignore
 
             partitions
 
-        let private prepareSortedRunsInPlace array keysArray = 
+        let private prepareSortedRunsInPlace array keysArray =
             let partitions = createPartitions array
             let keyComparer = LanguagePrimitives.FastGenericComparerCanBeNull
-            Parallel.For(0,partitions.Length,fun i ->  
-                Array.Sort<_,_>(keysArray, array,partitions[i].Offset,partitions[i].Count, keyComparer) 
-                ) |> ignore
+
+            Parallel.For(
+                0,
+                partitions.Length,
+                fun i -> Array.Sort<_, _>(keysArray, array, partitions[i].Offset, partitions[i].Count, keyComparer)
+            )
+            |> ignore
 
             partitions
 
-        let inline swap leftIdx rightIdx (array:'T[]) = 
+        let inline swap leftIdx rightIdx (array: 'T[]) =
             let temp = array[leftIdx]
             array[leftIdx] <- array[rightIdx]
             array[rightIdx] <- temp
 
-        let private mergeTwoSortedConsequtiveSegmentsInPlaceByKeys (keysArray:'TKey[]) (left:ArraySegment<'T>) (right: ArraySegment<'T>)  =                   
-            let mutable leftIdx = left.Offset       
-            let leftMax,rightMax = left.Offset+left.Count, right.Offset+right.Count
+        let private mergeTwoSortedConsequtiveSegmentsInPlaceByKeys
+            (keysArray: 'TKey[])
+            (left: ArraySegment<'T>)
+            (right: ArraySegment<'T>)
+            =
+            let mutable leftIdx = left.Offset
+            let leftMax, rightMax = left.Offset + left.Count, right.Offset + right.Count
 
-            while leftIdx<leftMax  do
-                while (leftIdx<leftMax) && (compare keysArray[leftIdx] keysArray[right.Offset]) <= 0 do
+            while leftIdx < leftMax do
+                while (leftIdx < leftMax) && (compare keysArray[leftIdx] keysArray[right.Offset]) <= 0 do
                     leftIdx <- leftIdx + 1
 
-                let leftMostUnprocessed = keysArray[leftIdx]        
+                let leftMostUnprocessed = keysArray[leftIdx]
                 let mutable writableRightIdx = right.Offset
-                while (writableRightIdx < rightMax) && (compare leftMostUnprocessed keysArray[writableRightIdx]) > 0 do   
+
+                while (writableRightIdx < rightMax)
+                      && (compare leftMostUnprocessed keysArray[writableRightIdx]) > 0 do
                     keysArray |> swap leftIdx writableRightIdx
                     left.Array |> swap leftIdx writableRightIdx
                     writableRightIdx <- writableRightIdx + 1
@@ -2138,92 +2151,118 @@ module Array =
 
             new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
 
-        let private mergeTwoSortedConsequtiveSegmentsInPlaceWith (comparer:IComparer<'T>) (left:ArraySegment<'T>) (right: ArraySegment<'T>)  =        
-            let mutable leftIdx = left.Offset       
-            let leftMax,rightMax,fullArray = left.Offset+left.Count, right.Offset+right.Count, left.Array
+        let private mergeTwoSortedConsequtiveSegmentsInPlaceWith
+            (comparer: IComparer<'T>)
+            (left: ArraySegment<'T>)
+            (right: ArraySegment<'T>)
+            =
+            let mutable leftIdx = left.Offset
+
+            let leftMax, rightMax, fullArray =
+                left.Offset + left.Count, right.Offset + right.Count, left.Array
 
-            while leftIdx<leftMax  do
-                while (leftIdx<leftMax) && comparer.Compare(fullArray[leftIdx],fullArray[right.Offset]) <= 0 do
+            while leftIdx < leftMax do
+                while (leftIdx < leftMax)
+                      && comparer.Compare(fullArray[leftIdx], fullArray[right.Offset]) <= 0 do
                     leftIdx <- leftIdx + 1
 
-                let leftMostUnprocessed = fullArray[leftIdx]        
+                let leftMostUnprocessed = fullArray[leftIdx]
                 let mutable writableRightIdx = right.Offset
-                while (writableRightIdx < rightMax) && comparer.Compare(leftMostUnprocessed,fullArray[writableRightIdx]) > 0 do                      
+
+                while (writableRightIdx < rightMax)
+                      && comparer.Compare(leftMostUnprocessed, fullArray[writableRightIdx]) > 0 do
                     fullArray |> swap leftIdx writableRightIdx
                     writableRightIdx <- writableRightIdx + 1
                     leftIdx <- leftIdx + 1
 
             new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
 
-        let rec mergeRunsInParallel (segmentsInOrder:ArraySegment<'T> []) pairwiseMerger = 
+        let rec mergeRunsInParallel (segmentsInOrder: ArraySegment<'T>[]) pairwiseMerger =
             match segmentsInOrder with
-            | [|singleRun|] ->  singleRun
-            | [|first;second|] -> pairwiseMerger first second
+            | [| singleRun |] -> singleRun
+            | [| first; second |] -> pairwiseMerger first second
             | [||] -> invalidArg "runs" LanguagePrimitives.ErrorStrings.InputArrayEmptyString
-            | threeOrMoreSegments ->   
+            | threeOrMoreSegments ->
                 let mutable left = None
                 let mutable right = None
-                let midIndex = threeOrMoreSegments.Length/2
-                Parallel.Invoke( 
-                    (fun () -> left <- Some (mergeRunsInParallel threeOrMoreSegments[0..midIndex-1] pairwiseMerger)),
-                    (fun () -> right <- Some (mergeRunsInParallel threeOrMoreSegments[midIndex..] pairwiseMerger)))
-                
+                let midIndex = threeOrMoreSegments.Length / 2
+
+                Parallel.Invoke(
+                    (fun () -> left <- Some(mergeRunsInParallel threeOrMoreSegments[0 .. midIndex - 1] pairwiseMerger)),
+                    (fun () -> right <- Some(mergeRunsInParallel threeOrMoreSegments[midIndex..] pairwiseMerger))
+                )
+
                 pairwiseMerger left.Value right.Value
 
         [<CompiledName("SortInPlaceWith")>]
         let sortInPlaceWith comparer (array: 'T[]) =
             checkNonNull "array" array
-            let comparer = ComparisonIdentity.FromFunction(comparer)  
-            if array.Length < sequentialCutoffForSorting then       
+            let comparer = ComparisonIdentity.FromFunction(comparer)
+
+            if array.Length < sequentialCutoffForSorting then
                 Array.Sort(array, comparer)
             else
-                let preSortedPartitions = prepareSortedRunsInPlaceWith array comparer 
-                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceWith comparer) |> ignore
+                let preSortedPartitions = prepareSortedRunsInPlaceWith array comparer
+
+                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceWith comparer)
+                |> ignore
 
         [<CompiledName("SortInPlaceBy")>]
         let sortInPlaceBy (projection: 'T -> 'U) (array: 'T[]) =
             checkNonNull "array" array
+
             if array.Length < sequentialCutoffForSorting then
                 Microsoft.FSharp.Primitives.Basics.Array.unstableSortInPlaceBy projection array
-            else              
+            else
                 let projectedFields = map projection array
-                let preSortedPartitions = prepareSortedRunsInPlace array projectedFields            
-                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceByKeys projectedFields) |> ignore        
-            
+                let preSortedPartitions = prepareSortedRunsInPlace array projectedFields
+
+                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceByKeys projectedFields)
+                |> ignore
+
         [<CompiledName("SortInPlace")>]
         let sortInPlace (array: 'T[]) =
-            checkNonNull "array" array          
+            checkNonNull "array" array
+
             if array.Length < sequentialCutoffForSorting then
                 Microsoft.FSharp.Primitives.Basics.Array.unstableSortInPlace array
-            else            
-                let preSortedPartitions = prepareSortedRunsInPlaceWith array LanguagePrimitives.FastGenericComparerCanBeNull
-                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceWith LanguagePrimitives.FastGenericComparer) |> ignore
+            else
+                let preSortedPartitions =
+                    prepareSortedRunsInPlaceWith array LanguagePrimitives.FastGenericComparerCanBeNull
+
+                mergeRunsInParallel
+                    preSortedPartitions
+                    (mergeTwoSortedConsequtiveSegmentsInPlaceWith LanguagePrimitives.FastGenericComparer)
+                |> ignore
 
         [<CompiledName("SortWith")>]
-        let sortWith (comparer: 'T -> 'T -> int) (array: 'T[]) =           
+        let sortWith (comparer: 'T -> 'T -> int) (array: 'T[]) =
             let result = copy array
             sortInPlaceWith comparer result
             result
 
         [<CompiledName("SortBy")>]
-        let sortBy projection array =         
+        let sortBy projection array =
             let result = copy array
             sortInPlaceBy projection result
             result
 
         [<CompiledName("Sort")>]
-        let sort array =        
+        let sort array =
             let result = copy array
             sortInPlace result
             result
 
         [<CompiledName("SortByDescending")>]
         let inline sortByDescending projection array =
-            let inline compareDescending a b = compare (projection b) (projection a)            
+            let inline compareDescending a b =
+                compare (projection b) (projection a)
+
             sortWith compareDescending array
 
         [<CompiledName("SortDescending")>]
-        let inline sortDescending array =   
-            let inline compareDescending a b = compare b a
+        let inline sortDescending array =
+            let inline compareDescending a b =
+                compare b a
+
             sortWith compareDescending array
-         

From 0fefc1ac77b6070c7fc05a87b33bf09d73f4d253 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Tue, 28 Feb 2023 10:00:31 +0100
Subject: [PATCH 03/13] Updating FsharpCore baselines

---
 .../FSharp.Core.SurfaceArea.netstandard20.debug.bsl       | 8 ++++++++
 .../FSharp.Core.SurfaceArea.netstandard20.release.bsl     | 8 ++++++++
 .../FSharp.Core.SurfaceArea.netstandard21.debug.bsl       | 8 ++++++++
 .../FSharp.Core.SurfaceArea.netstandard21.release.bsl     | 8 ++++++++
 4 files changed, 32 insertions(+)

diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl
index 348c413de1b..7480b0ca540 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl
@@ -46,8 +46,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Collect[T,TResult](
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl
index 2d5ddb40c7a..4f9de88bc8b 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl
@@ -46,8 +46,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Collect[T,TResult](
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl
index 45805d53ae0..08bc157a753 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl
@@ -46,8 +46,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Collect[T,TResult](
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl
index 275087e02e0..6f2d3d73a0a 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl
@@ -46,8 +46,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Collect[T,TResult](
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[])
 Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[])
+Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[])
 Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[])

From c1c7f723d45b087db347bbcb620695ccdc7c548f Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Thu, 9 Mar 2023 15:23:38 +0100
Subject: [PATCH 04/13] Adding [<Experimental("Experimental library feature,
 requires '--langversion:preview'")>]

---
 src/FSharp.Core/array.fsi | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi
index 34d6a97ccfc..c9c00cf91d9 100644
--- a/src/FSharp.Core/array.fsi
+++ b/src/FSharp.Core/array.fsi
@@ -3328,6 +3328,7 @@ module Array =
         /// Evaluates to <c>[| 1; 1 3; 4; 6; 8 |]</c>.
         /// </example>
         [<CompiledName("Sort")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val sort: array:'T[] -> 'T[] when 'T : comparison 
 
         /// <summary>Sorts the elements of an array in parallel, using the given projection for the keys and returning a new array. 
@@ -3353,6 +3354,7 @@ module Array =
         /// </example>
 
         [<CompiledName("SortBy")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val sortBy: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison 
 
         /// <summary>Sorts the elements of an array in parallel, using the given comparison function as the order, returning a new array.</summary>
@@ -3381,6 +3383,7 @@ module Array =
         /// Evaluates to <c>[|(0, "aa"); (2, "cc"); (3, "dd"); (1, "bbb")|]</c>.
         /// </example>
         [<CompiledName("SortWith")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val sortWith: comparer:('T -> 'T -> int) -> array:'T[] -> 'T[]
 
         /// <summary>Sorts the elements of an array by mutating the array in-place in parallel, using the given projection for the keys. 
@@ -3403,6 +3406,7 @@ module Array =
         /// After evaluation <c>array</c> contains <c>[|"a"; "dd"; "bbb"; "cccc"|]</c>.
         /// </example>
         [<CompiledName("SortInPlaceBy")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val sortInPlaceBy: projection:('T -> 'Key) -> array:'T[] -> unit when 'Key : comparison 
 
         /// <summary>Sorts the elements of an array by mutating the array in-place in parallel, using the given comparison function as the order.</summary>
@@ -3426,6 +3430,7 @@ module Array =
         /// After evaluation <c>array</c> contains <c>[|(0, "aa"); (2, "cc"); (3, "dd"); (1, "bbb")|]</c>.
         /// </example>
         [<CompiledName("SortInPlaceWith")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val sortInPlaceWith: comparer:('T -> 'T -> int) -> array:'T[] -> unit
 
         /// <summary>Sorts the elements of an array by mutating the array in-place in parallel, using the given comparison function. 
@@ -3444,6 +3449,7 @@ module Array =
         /// After evaluation <c>array</c> contains <c>[| 1; 1; 3; 4; 6; 8 |]</c>.
         /// </example>
         [<CompiledName("SortInPlace")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val sortInPlace: array:'T[] -> unit when 'T : comparison 
 
         /// <summary>Sorts the elements of an array in parallel, in descending order, returning a new array. Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>. </summary>
@@ -3464,6 +3470,7 @@ module Array =
         /// Evaluates to <c>[| 8; 6; 4; 3; 1; 1 |]</c>.
         /// </example>
         [<CompiledName("SortDescending")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val inline sortDescending: array:'T[] -> 'T[] when 'T : comparison
 
         /// <summary>Sorts the elements of an array in parallel, in descending order, using the given projection for the keys and returning a new array. 
@@ -3486,4 +3493,5 @@ module Array =
         /// Evaluates to <c>[|"cccc"; "bbb"; "dd"; "a"|]</c>.
         /// </example>
         [<CompiledName("SortByDescending")>]
+        [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
         val inline sortByDescending: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison

From 72d1fcdeb09819370711969e1f71ebb5e7fba0ce Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Thu, 9 Mar 2023 16:23:59 +0100
Subject: [PATCH 05/13] sorting tests added

---
 .../ArrayModule2.fs                           | 141 ++++++++++++++++++
 .../CollectionModulesConsistency.fs           |  84 +++++++++++
 2 files changed, 225 insertions(+)

diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
index 23510ddb781..dac56d7e653 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
@@ -644,7 +644,49 @@ type ArrayModule2() =
         Assert.AreEqual([|8;8;8|], eights)
         
         ()   
+
+
+    member private _.MultiplyArray(template:_[],repetitions:int) = 
+        Array.zeroCreate repetitions |> Array.collect (fun _ -> template)     
+
+    member private _.CompareTwoMethods(initialData,regularArrayFunc,arrayParaFunc) = 
+        let first,second = initialData, Array.copy initialData
+        let whenSequential = regularArrayFunc second
+        let whenParallel = arrayParaFunc first
+
+        Assert.AreEqual(whenSequential, whenParallel)
+
+    [<Fact>]
+    member this.sortInPlaceWithParallel() =      
+
+        let tee f x = f x; x
+        // integer array  
+        this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))
+
+        // Sort backwards
+        this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith (fun a b -> -1 * compare a b)),tee (Array.Parallel.sortInPlaceWith (fun a b -> -1 * compare a b)))
         
+        // string array
+        let strArr = [|"Lists"; "are"; "a"; "commonly"; "used"; "data"; "structure"|]    
+        this.MultiplyArray(strArr,1_000) 
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))
+        
+        // empty array
+        [| |]
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))       
+        
+        // null array
+        let nullArr = null:string[]      
+        CheckThrowsArgumentNullException (fun () -> Array.Parallel.sortInPlaceWith compare nullArr  |> ignore)         
+
+        
+        // Equal elements              
+        this.MultiplyArray([|8; 8;8|],1_000) 
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))
+        
+        ()           
 
     [<Fact>]
     member this.sortInPlaceBy() =
@@ -675,6 +717,31 @@ type ArrayModule2() =
         Assert.AreEqual([|3;8|],len2Arr)  
         
         () 
+
+    [<Fact>]
+    member this.sortInPlaceByParallel() =
+        let tee f x = f x; x
+
+        // integer array  
+        this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceBy int),tee (Array.Parallel.sortInPlaceBy int))
+        
+        // string array
+        let strArr = [|"Lists"; "are"; "a"; "commonly"; "used"; "datastructure"|]    
+        this.MultiplyArray(strArr,1_000) 
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceBy (fun (x:string) -> x.Length)),tee (Array.Parallel.sortInPlaceBy (fun (x:string) -> x.Length)))
+        
+        
+        // empty array
+        let emptyArr:int[] = [| |]
+        Array.Parallel.sortInPlaceBy int emptyArr
+        if emptyArr <> [||] then Assert.Fail()
+        
+        // null array
+        let nullArr = null:string[]      
+        CheckThrowsArgumentNullException (fun () -> Array.Parallel.sortInPlaceBy (fun (x:string) -> x.Length) nullArr |> ignore)  
+
+        () 
         
     [<Fact>]
     member this.SortDescending() =
@@ -711,6 +778,40 @@ type ArrayModule2() =
         Assert.AreEqual([| maxFloat; 2.0; 1.5; 1.0; 0.5; epsilon; 0.0; -epsilon; minFloat; |], resultFloat)
 
         () 
+
+    [<Fact>]
+    member this.SortDescendingParallel() =
+        // integer array  
+        this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
+        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)
+        
+        // string Array
+        this.MultiplyArray([|"Z";"a";"d"; ""; "Y"; null; "c";"b";"X"|]  ,1_000) 
+        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)
+        
+        // empty array
+        let emptyArr:int[] = [| |]
+        let resultEmpty = Array.Parallel.sortDescending emptyArr
+        if resultEmpty <> [||] then Assert.Fail()
+        
+        // tuple array
+        let tupArr = [|(2,"a");(1,"d");(1,"b");(1,"a");(2,"x");(2,"b");(1,"x")|]   
+        this.MultiplyArray(tupArr,1_000) 
+        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)       
+
+        // date array
+        let dateArr = [|DateTime(2014,12,31);DateTime(2014,1,1);DateTime(2015,1,1);DateTime(2013,12,31);DateTime(2014,1,1)|]       
+        this.MultiplyArray(dateArr,1_000) 
+        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)    
+        Assert.AreEqual([|DateTime(2014,12,31);DateTime(2014,1,1);DateTime(2015,1,1);DateTime(2013,12,31);DateTime(2014,1,1)|], dateArr)
+
+        // float array
+        let minFloat,maxFloat,epsilon = System.Double.MinValue,System.Double.MaxValue,System.Double.Epsilon
+        let floatArr = [| 0.0; 0.5; 2.0; 1.5; 1.0; minFloat; maxFloat; epsilon; -epsilon |]
+        this.MultiplyArray(floatArr,1_000) 
+        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int) 
+
+        () 
         
     [<Fact>]
     member this.SortByDescending() =
@@ -750,6 +851,46 @@ type ArrayModule2() =
         Assert.AreEqual([| maxFloat; 2.0; 1.5; 1.0; 0.5; epsilon; 0.0; -epsilon; minFloat; |], resultFloat)
 
         ()  
+
+    [<Fact>]
+    member this.SortByDescendingParallel() =
+        // integer array  
+        let intArr = [|3;5;7;2;4;8|]
+        this.MultiplyArray(intArr,1_000)
+        |> this.CompareTwoMethods(Array.sortByDescending int,Array.Parallel.sortByDescending int)      
+        Assert.AreEqual([|3;5;7;2;4;8|], intArr)
+
+                
+        // string array
+        let strArr = [|".."; ""; "..."; "."; "...."|]    
+        this.MultiplyArray(strArr,1_000)
+        |> this.CompareTwoMethods(Array.sortByDescending (fun (x:string) -> x.Length),Array.Parallel.sortByDescending (fun (x:string) -> x.Length))    
+        Assert.AreEqual([|".."; ""; "..."; "."; "...."|], strArr)
+        
+        // empty array
+        let emptyArr:int[] = [| |]
+        let resultEmpty = Array.Parallel.sortByDescending int emptyArr        
+        if resultEmpty <> [||] then Assert.Fail()    
+        
+        // tuple array
+        let tupArr = [|(2,"a");(1,"d");(1,"b");(2,"x")|]
+        this.MultiplyArray(tupArr,1_000)
+        |> this.CompareTwoMethods(Array.sortByDescending snd,Array.Parallel.sortByDescending snd)       
+        Assert.AreEqual( [|(2,"a");(1,"d");(1,"b");(2,"x")|] , tupArr)  
+        
+        // date array
+        let dateArr = [|DateTime(2013,12,31);DateTime(2014,2,1);DateTime(2015,1,1);DateTime(2014,3,1)|]
+        this.MultiplyArray(dateArr,1_000)
+        |> this.CompareTwoMethods(Array.sortByDescending (fun (d:DateTime) -> d.Month),Array.Parallel.sortByDescending (fun (d:DateTime) -> d.Month))         
+        Assert.AreEqual([|DateTime(2013,12,31);DateTime(2014,2,1);DateTime(2015,1,1);DateTime(2014,3,1)|], dateArr)     
+
+        // float array
+        let minFloat,maxFloat,epsilon = System.Double.MinValue,System.Double.MaxValue,System.Double.Epsilon
+        let floatArr = [| 0.0; 0.5; 2.0; 1.5; 1.0; minFloat; maxFloat; epsilon; -epsilon |]
+        this.MultiplyArray(floatArr,1_000)
+        |> this.CompareTwoMethods(Array.sortByDescending id,Array.Parallel.sortByDescending snd)
+
+        () 
          
     [<Fact>]
     member this.Sub() =
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
index 1560c16b564..c8258b9e5a4 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
@@ -8,6 +8,7 @@ open FsCheck
 open Utils
 
 let smallerSizeCheck testable = Check.One({ Config.QuickThrowOnFailure with EndSize = 25 }, testable)
+let bigSizeCheck testable = Check.One({ Config.QuickThrowOnFailure with StartSize = 4_096;EndSize = 30_000 }, testable)
 
 /// helper function that creates labeled FsCheck properties for equality comparisons
 let consistency name sqs ls arr =
@@ -992,6 +993,7 @@ let ``sortByDescending actually sorts (but is inconsistent in regards of stabili
     smallerSizeCheck sortByDescending<string,int>
     smallerSizeCheck sortByDescending<NormalFloat,int>
 
+
 let sum (xs : int []) =
     let s = run (fun () -> xs |> Seq.sum)
     let l = run (fun () -> xs |> Array.toList |> List.sum)
@@ -1304,3 +1306,85 @@ let ``zip3 is consistent for collections with equal length`` () =
     smallerSizeCheck zip3<int>
     smallerSizeCheck zip3<string>
     smallerSizeCheck zip3<NormalFloat>
+
+
+module ArrayParallelVsArray = 
+    let sort<'a when 'a : comparison> (xs : 'a []) =
+        let a = xs |> Array.sort
+        let pa = xs |> Array.Parallel.sort
+
+        let opName = "sort"
+        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a name pa)
+
+    [<Fact>]
+    let ``sort is consistent`` () =
+        bigSizeCheck sort<int>
+        bigSizeCheck sort<string>
+        bigSizeCheck sort<NormalFloat>
+
+    let sortBy<'a,'b when 'a : comparison and 'b : comparison> (xs : 'a []) (f:'a -> 'b) =
+        let a = xs |> Array.sortBy f
+        let pa = xs |> Array.Parallel.sortBy f
+
+        isSorted (Array.map f a) && isSorted (Array.map f pa) &&
+          haveSameElements pa xs && haveSameElements a xs &&
+          a.Length = pa.Length && a.Length = xs.Length
+
+    [<Fact>]
+    let ``sortBy actually sorts (but is inconsistent in regards of stability)`` () =
+        bigSizeCheck sortBy<int,int>
+        bigSizeCheck sortBy<int,string>
+        bigSizeCheck sortBy<string,string>
+        bigSizeCheck sortBy<string,int>
+        bigSizeCheck sortBy<NormalFloat,int>
+
+    let sortWith<'a,'b when 'a : comparison and 'b : comparison> (xs : 'a []) =
+        let f x y = 
+            if x = y then 0 else
+            if x = Unchecked.defaultof<_> && y <> Unchecked.defaultof<_> then -1 else
+            if y = Unchecked.defaultof<_> && x <> Unchecked.defaultof<_> then 1 else
+            if x < y then -1 else 1
+
+        let a = xs |> Array.sortWith f
+        let pa = xs |> Array.Parallel.sortWith f
+        let isSorted sorted = sorted |> Array.pairwise |> Array.forall (fun (a,b) -> f a b <= 0 || a = b)
+
+        isSorted a && isSorted pa &&
+            haveSameElements pa xs && haveSameElements a xs &&
+            a.Length = pa.Length && a.Length = xs.Length
+
+    [<Fact>]
+    let ``sortWith actually sorts (but is inconsistent in regards of stability)`` () =
+       bigSizeCheck sortWith<int,int>
+       bigSizeCheck sortWith<int,string>
+       bigSizeCheck sortWith<string,string>
+       bigSizeCheck sortWith<string,int>
+       bigSizeCheck sortWith<NormalFloat,int>
+
+    let sortDescending<'a when 'a : comparison> (xs : 'a []) =
+        let a = xs |> Array.sortDescending
+        let pa = xs |> Array.Parallel.sortDescending
+        let opName = "sortDescending"
+        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a name pa)
+
+    [<Fact>]
+    let ``sortDescending is consistent`` () =
+        bigSizeCheck sortDescending<int>
+        bigSizeCheck sortDescending<string>
+        bigSizeCheck sortDescending<NormalFloat>
+
+    let sortByDescending<'a,'b when 'a : comparison and 'b : comparison> (xs : 'a []) (f:'a -> 'b) =
+        let a = xs |> Array.sortByDescending f
+        let pa = xs |> Array.Parallel.sortByDescending f
+
+        isSorted (Array.map pa l |> Array.rev) && isSorted (Array.map f a |> Array.rev) &&
+          haveSameElements pa xs && haveSameElements a xs &&
+          a.Length = pa.Length && a.Length = xs.Length
+
+    [<Fact>]
+    let ``sortByDescending actually sorts (but is inconsistent in regards of stability)`` () =
+        bigSizeCheck sortByDescending<int,int>
+        bigSizeCheck sortByDescending<int,string>
+        bigSizeCheck sortByDescending<string,string>
+        bigSizeCheck sortByDescending<string,int>
+        bigSizeCheck sortByDescending<NormalFloat,int>
\ No newline at end of file

From c2d2f5088bdfb8a657307d3dfd8a4a4972b30300 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Thu, 9 Mar 2023 18:51:35 +0100
Subject: [PATCH 06/13] sorting tests added, bug fixed

---
 src/FSharp.Core/array.fs                      | 86 ++++++++++++-------
 .../ArrayModule2.fs                           | 52 ++++++-----
 .../CollectionModulesConsistency.fs           |  6 +-
 3 files changed, 86 insertions(+), 58 deletions(-)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index e2f7b90951d..9b6f570dee7 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2122,33 +2122,46 @@ module Array =
 
             partitions
 
-        let inline swap leftIdx rightIdx (array: 'T[]) =
-            let temp = array[leftIdx]
-            array[leftIdx] <- array[rightIdx]
-            array[rightIdx] <- temp
+        //let inline swap leftIdx rightIdx (array: 'T[]) =
+        //    let temp = array[leftIdx]
+        //    array[leftIdx] <- array[rightIdx]
+        //    array[rightIdx] <- temp
 
         let private mergeTwoSortedConsequtiveSegmentsInPlaceByKeys
             (keysArray: 'TKey[])
             (left: ArraySegment<'T>)
             (right: ArraySegment<'T>)
             =
-            let mutable leftIdx = left.Offset
-            let leftMax, rightMax = left.Offset + left.Count, right.Offset + right.Count
+            assert(left.Offset + left.Count = right.Offset)
+            assert(Object.ReferenceEquals(left.Array,right.Array))
+            assert(right.Offset + right.Count <= keysArray.Length)
 
-            while leftIdx < leftMax do
-                while (leftIdx < leftMax) && (compare keysArray[leftIdx] keysArray[right.Offset]) <= 0 do
-                    leftIdx <- leftIdx + 1
+            let mutable leftIdx,rightIdx = left.Offset, right.Offset
+            let rightMax,fullArray = right.Offset + right.Count, left.Array
 
-                let leftMostUnprocessed = keysArray[leftIdx]
-                let mutable writableRightIdx = right.Offset
+            if keysArray[rightIdx-1] <= keysArray[rightIdx] then
+                ()
+            else
+                while leftIdx < rightIdx && rightIdx < rightMax do
+                    if keysArray[leftIdx] <= keysArray[rightIdx] then
+                        leftIdx <- leftIdx + 1
+                    else
+                        let rightKey,rightValue = keysArray[rightIdx],fullArray[rightIdx]                 
+                        let mutable whereShouldFirstOfRightGo = rightIdx
+
+                        // Bubble-down the 1st element of right segment to its correct position
+                        while whereShouldFirstOfRightGo <> leftIdx do
+                            keysArray[whereShouldFirstOfRightGo] <- keysArray[whereShouldFirstOfRightGo - 1]
+                            fullArray[whereShouldFirstOfRightGo] <- fullArray[whereShouldFirstOfRightGo - 1]
+                            whereShouldFirstOfRightGo <- whereShouldFirstOfRightGo - 1
+
+                        keysArray[leftIdx] <- rightKey
+                        fullArray[leftIdx] <- rightValue
 
-                while (writableRightIdx < rightMax)
-                      && (compare leftMostUnprocessed keysArray[writableRightIdx]) > 0 do
-                    keysArray |> swap leftIdx writableRightIdx
-                    left.Array |> swap leftIdx writableRightIdx
-                    writableRightIdx <- writableRightIdx + 1
-                    leftIdx <- leftIdx + 1
+                        leftIdx <- leftIdx + 1
+                        rightIdx <- rightIdx + 1
 
+       
             new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
 
         let private mergeTwoSortedConsequtiveSegmentsInPlaceWith
@@ -2156,24 +2169,31 @@ module Array =
             (left: ArraySegment<'T>)
             (right: ArraySegment<'T>)
             =
-            let mutable leftIdx = left.Offset
+            assert(left.Offset + left.Count = right.Offset)
+            assert(Object.ReferenceEquals(left.Array,right.Array))   
 
-            let leftMax, rightMax, fullArray =
-                left.Offset + left.Count, right.Offset + right.Count, left.Array
+            let mutable leftIdx,rightIdx = left.Offset, right.Offset
+            let rightMax,fullArray = right.Offset + right.Count, left.Array
 
-            while leftIdx < leftMax do
-                while (leftIdx < leftMax)
-                      && comparer.Compare(fullArray[leftIdx], fullArray[right.Offset]) <= 0 do
-                    leftIdx <- leftIdx + 1
-
-                let leftMostUnprocessed = fullArray[leftIdx]
-                let mutable writableRightIdx = right.Offset
-
-                while (writableRightIdx < rightMax)
-                      && comparer.Compare(leftMostUnprocessed, fullArray[writableRightIdx]) > 0 do
-                    fullArray |> swap leftIdx writableRightIdx
-                    writableRightIdx <- writableRightIdx + 1
-                    leftIdx <- leftIdx + 1
+            if comparer.Compare(fullArray[rightIdx-1], fullArray[rightIdx]) <= 0 then
+                ()
+            else
+                while leftIdx < rightIdx && rightIdx < rightMax do
+                    if comparer.Compare(fullArray[leftIdx], fullArray[rightIdx]) <= 0 then
+                        leftIdx <- leftIdx + 1
+                    else
+                        let rightValue = fullArray[rightIdx]                 
+                        let mutable whereShouldFirstOfRightGo = rightIdx
+
+                        // Bubble-down the 1st element of right segment to its correct position
+                        while whereShouldFirstOfRightGo <> leftIdx do                          
+                            fullArray[whereShouldFirstOfRightGo] <- fullArray[whereShouldFirstOfRightGo - 1]
+                            whereShouldFirstOfRightGo <- whereShouldFirstOfRightGo - 1
+                       
+                        fullArray[leftIdx] <- rightValue
+
+                        leftIdx <- leftIdx + 1
+                        rightIdx <- rightIdx + 1           
 
             new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
 
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
index dac56d7e653..4a36cd1410d 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
@@ -649,12 +649,21 @@ type ArrayModule2() =
     member private _.MultiplyArray(template:_[],repetitions:int) = 
         Array.zeroCreate repetitions |> Array.collect (fun _ -> template)     
 
-    member private _.CompareTwoMethods(initialData,regularArrayFunc,arrayParaFunc) = 
+    member private _.CompareTwoMethods<'TIn,'TOut> (regularArrayFunc:'TIn[]->'TOut[]) (arrayParaFunc:'TIn[]->'TOut[]) (initialData:'TIn[]) = 
         let first,second = initialData, Array.copy initialData
         let whenSequential = regularArrayFunc second
         let whenParallel = arrayParaFunc first
 
-        Assert.AreEqual(whenSequential, whenParallel)
+        if(whenSequential <> whenParallel) then
+            Assert.AreEqual(whenSequential.Length, whenParallel.Length, "Lengths are different")
+            let diffsAt = 
+                Array.zip whenSequential whenParallel
+                |> Array.mapi (fun idx (a,b) -> if(a <> b) then Some(idx,(a,b)) else None)
+                |> Array.choose id
+                |> dict
+            
+            Assert.Empty(diffsAt)
+            Assert.Equal<'TOut>(whenSequential, whenParallel)
 
     [<Fact>]
     member this.sortInPlaceWithParallel() =      
@@ -662,29 +671,28 @@ type ArrayModule2() =
         let tee f x = f x; x
         // integer array  
         this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
-        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare))
 
         // Sort backwards
         this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
-        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith (fun a b -> -1 * compare a b)),tee (Array.Parallel.sortInPlaceWith (fun a b -> -1 * compare a b)))
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith (fun a b -> -1 * compare a b))) (tee (Array.Parallel.sortInPlaceWith (fun a b -> -1 * compare a b)))
         
         // string array
         let strArr = [|"Lists"; "are"; "a"; "commonly"; "used"; "data"; "structure"|]    
         this.MultiplyArray(strArr,1_000) 
-        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare))
         
         // empty array
         [| |]
-        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))       
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare))       
         
         // null array
         let nullArr = null:string[]      
-        CheckThrowsArgumentNullException (fun () -> Array.Parallel.sortInPlaceWith compare nullArr  |> ignore)         
+        CheckThrowsArgumentNullException (fun () -> Array.Parallel.sortInPlaceWith compare nullArr  |> ignore)  
 
-        
         // Equal elements              
         this.MultiplyArray([|8; 8;8|],1_000) 
-        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare),tee (Array.Parallel.sortInPlaceWith compare))
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare))
         
         ()           
 
@@ -723,13 +731,13 @@ type ArrayModule2() =
         let tee f x = f x; x
 
         // integer array  
-        this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
-        |> this.CompareTwoMethods (tee (Array.sortInPlaceBy int),tee (Array.Parallel.sortInPlaceBy int))
+        this.MultiplyArray([|3;5;7;2;4;8|],50) 
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceBy int)) (tee (Array.Parallel.sortInPlaceBy int))
         
         // string array
         let strArr = [|"Lists"; "are"; "a"; "commonly"; "used"; "datastructure"|]    
         this.MultiplyArray(strArr,1_000) 
-        |> this.CompareTwoMethods (tee (Array.sortInPlaceBy (fun (x:string) -> x.Length)),tee (Array.Parallel.sortInPlaceBy (fun (x:string) -> x.Length)))
+        |> this.CompareTwoMethods (tee (Array.sortInPlaceBy (fun (x:string) -> x.Length))) (tee (Array.Parallel.sortInPlaceBy (fun (x:string) -> x.Length)))
         
         
         // empty array
@@ -783,11 +791,11 @@ type ArrayModule2() =
     member this.SortDescendingParallel() =
         // integer array  
         this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
-        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)
+        |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending)
         
         // string Array
         this.MultiplyArray([|"Z";"a";"d"; ""; "Y"; null; "c";"b";"X"|]  ,1_000) 
-        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)
+        |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending)
         
         // empty array
         let emptyArr:int[] = [| |]
@@ -797,19 +805,19 @@ type ArrayModule2() =
         // tuple array
         let tupArr = [|(2,"a");(1,"d");(1,"b");(1,"a");(2,"x");(2,"b");(1,"x")|]   
         this.MultiplyArray(tupArr,1_000) 
-        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)       
+        |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending)       
 
         // date array
         let dateArr = [|DateTime(2014,12,31);DateTime(2014,1,1);DateTime(2015,1,1);DateTime(2013,12,31);DateTime(2014,1,1)|]       
         this.MultiplyArray(dateArr,1_000) 
-        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int)    
+        |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending)    
         Assert.AreEqual([|DateTime(2014,12,31);DateTime(2014,1,1);DateTime(2015,1,1);DateTime(2013,12,31);DateTime(2014,1,1)|], dateArr)
 
         // float array
         let minFloat,maxFloat,epsilon = System.Double.MinValue,System.Double.MaxValue,System.Double.Epsilon
         let floatArr = [| 0.0; 0.5; 2.0; 1.5; 1.0; minFloat; maxFloat; epsilon; -epsilon |]
         this.MultiplyArray(floatArr,1_000) 
-        |> this.CompareTwoMethods (Array.sortDescending int,Array.Parallel.sortDescending int) 
+        |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending) 
 
         () 
         
@@ -857,14 +865,14 @@ type ArrayModule2() =
         // integer array  
         let intArr = [|3;5;7;2;4;8|]
         this.MultiplyArray(intArr,1_000)
-        |> this.CompareTwoMethods(Array.sortByDescending int,Array.Parallel.sortByDescending int)      
+        |> this.CompareTwoMethods(Array.sortByDescending int) (Array.Parallel.sortByDescending int)      
         Assert.AreEqual([|3;5;7;2;4;8|], intArr)
 
                 
         // string array
         let strArr = [|".."; ""; "..."; "."; "...."|]    
         this.MultiplyArray(strArr,1_000)
-        |> this.CompareTwoMethods(Array.sortByDescending (fun (x:string) -> x.Length),Array.Parallel.sortByDescending (fun (x:string) -> x.Length))    
+        |> this.CompareTwoMethods(Array.sortByDescending (fun (x:string) -> x.Length)) (Array.Parallel.sortByDescending (fun (x:string) -> x.Length))    
         Assert.AreEqual([|".."; ""; "..."; "."; "...."|], strArr)
         
         // empty array
@@ -875,20 +883,20 @@ type ArrayModule2() =
         // tuple array
         let tupArr = [|(2,"a");(1,"d");(1,"b");(2,"x")|]
         this.MultiplyArray(tupArr,1_000)
-        |> this.CompareTwoMethods(Array.sortByDescending snd,Array.Parallel.sortByDescending snd)       
+        |> this.CompareTwoMethods(Array.sortByDescending snd) (Array.Parallel.sortByDescending snd)       
         Assert.AreEqual( [|(2,"a");(1,"d");(1,"b");(2,"x")|] , tupArr)  
         
         // date array
         let dateArr = [|DateTime(2013,12,31);DateTime(2014,2,1);DateTime(2015,1,1);DateTime(2014,3,1)|]
         this.MultiplyArray(dateArr,1_000)
-        |> this.CompareTwoMethods(Array.sortByDescending (fun (d:DateTime) -> d.Month),Array.Parallel.sortByDescending (fun (d:DateTime) -> d.Month))         
+        |> this.CompareTwoMethods(Array.sortByDescending (fun (d:DateTime) -> d.Month)) (Array.Parallel.sortByDescending (fun (d:DateTime) -> d.Month))         
         Assert.AreEqual([|DateTime(2013,12,31);DateTime(2014,2,1);DateTime(2015,1,1);DateTime(2014,3,1)|], dateArr)     
 
         // float array
         let minFloat,maxFloat,epsilon = System.Double.MinValue,System.Double.MaxValue,System.Double.Epsilon
         let floatArr = [| 0.0; 0.5; 2.0; 1.5; 1.0; minFloat; maxFloat; epsilon; -epsilon |]
         this.MultiplyArray(floatArr,1_000)
-        |> this.CompareTwoMethods(Array.sortByDescending id,Array.Parallel.sortByDescending snd)
+        |> this.CompareTwoMethods(Array.sortByDescending id) (Array.Parallel.sortByDescending id)
 
         () 
          
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
index c8258b9e5a4..0ec1ceaadcb 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
@@ -1314,7 +1314,7 @@ module ArrayParallelVsArray =
         let pa = xs |> Array.Parallel.sort
 
         let opName = "sort"
-        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a name pa)
+        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a opName pa)
 
     [<Fact>]
     let ``sort is consistent`` () =
@@ -1365,7 +1365,7 @@ module ArrayParallelVsArray =
         let a = xs |> Array.sortDescending
         let pa = xs |> Array.Parallel.sortDescending
         let opName = "sortDescending"
-        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a name pa)
+        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a opName pa)
 
     [<Fact>]
     let ``sortDescending is consistent`` () =
@@ -1377,7 +1377,7 @@ module ArrayParallelVsArray =
         let a = xs |> Array.sortByDescending f
         let pa = xs |> Array.Parallel.sortByDescending f
 
-        isSorted (Array.map pa l |> Array.rev) && isSorted (Array.map f a |> Array.rev) &&
+        isSorted (Array.map f pa |> Array.rev) && isSorted (Array.map f a |> Array.rev) &&
           haveSameElements pa xs && haveSameElements a xs &&
           a.Length = pa.Length && a.Length = xs.Length
 

From bfef11e3452d99d6620405c5917e75e07519881d Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 13 Mar 2023 16:12:28 +0100
Subject: [PATCH 07/13] Turned mergeSort into pivot-based parallel sort

---
 src/FSharp.Core/array.fs | 279 ++++++++++++++++++---------------------
 1 file changed, 129 insertions(+), 150 deletions(-)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index 9b6f570dee7..1336d6b8462 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2075,9 +2075,8 @@ module Array =
 
         // The following two parameters were benchmarked and found to be optimal.
         // Benchmark was run using: 11th Gen Intel Core i9-11950H 2.60GHz, 1 CPU, 16 logical and 8 physical cores
-        let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use
-        let private sequentialCutoffForSorting = 2_500 // Arrays smaller then this will be sorted sequentially
-        let private minChunkSize = 64 // The minimum size of a chunk to be sorted in parallel
+        let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use  
+        let private minChunkSize = 256 // The minimum size of a chunk to be sorted in parallel
 
         let private createPartitions (array: 'T[]) =
             [|
@@ -2097,163 +2096,134 @@ module Array =
                     yield new ArraySegment<'T>(array, offset, array.Length - offset)
             |]
 
-        let private prepareSortedRunsInPlaceWith array comparer =
-            let partitions = createPartitions array
-
-            Parallel.For(
-                0,
-                partitions.Length,
-                fun i -> Array.Sort(array, partitions[i].Offset, partitions[i].Count, comparer)
-            )
-            |> ignore
-
-            partitions
-
-        let private prepareSortedRunsInPlace array keysArray =
-            let partitions = createPartitions array
-            let keyComparer = LanguagePrimitives.FastGenericComparerCanBeNull
-
-            Parallel.For(
-                0,
-                partitions.Length,
-                fun i -> Array.Sort<_, _>(keysArray, array, partitions[i].Offset, partitions[i].Count, keyComparer)
-            )
-            |> ignore
-
-            partitions
-
-        //let inline swap leftIdx rightIdx (array: 'T[]) =
-        //    let temp = array[leftIdx]
-        //    array[leftIdx] <- array[rightIdx]
-        //    array[rightIdx] <- temp
-
-        let private mergeTwoSortedConsequtiveSegmentsInPlaceByKeys
-            (keysArray: 'TKey[])
-            (left: ArraySegment<'T>)
-            (right: ArraySegment<'T>)
-            =
-            assert(left.Offset + left.Count = right.Offset)
-            assert(Object.ReferenceEquals(left.Array,right.Array))
-            assert(right.Offset + right.Count <= keysArray.Length)
-
-            let mutable leftIdx,rightIdx = left.Offset, right.Offset
-            let rightMax,fullArray = right.Offset + right.Count, left.Array
-
-            if keysArray[rightIdx-1] <= keysArray[rightIdx] then
-                ()
-            else
-                while leftIdx < rightIdx && rightIdx < rightMax do
-                    if keysArray[leftIdx] <= keysArray[rightIdx] then
-                        leftIdx <- leftIdx + 1
-                    else
-                        let rightKey,rightValue = keysArray[rightIdx],fullArray[rightIdx]                 
-                        let mutable whereShouldFirstOfRightGo = rightIdx
-
-                        // Bubble-down the 1st element of right segment to its correct position
-                        while whereShouldFirstOfRightGo <> leftIdx do
-                            keysArray[whereShouldFirstOfRightGo] <- keysArray[whereShouldFirstOfRightGo - 1]
-                            fullArray[whereShouldFirstOfRightGo] <- fullArray[whereShouldFirstOfRightGo - 1]
-                            whereShouldFirstOfRightGo <- whereShouldFirstOfRightGo - 1
-
-                        keysArray[leftIdx] <- rightKey
-                        fullArray[leftIdx] <- rightValue
-
-                        leftIdx <- leftIdx + 1
-                        rightIdx <- rightIdx + 1
-
-       
-            new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
-
-        let private mergeTwoSortedConsequtiveSegmentsInPlaceWith
-            (comparer: IComparer<'T>)
-            (left: ArraySegment<'T>)
-            (right: ArraySegment<'T>)
-            =
-            assert(left.Offset + left.Count = right.Offset)
-            assert(Object.ReferenceEquals(left.Array,right.Array))   
-
-            let mutable leftIdx,rightIdx = left.Offset, right.Offset
-            let rightMax,fullArray = right.Offset + right.Count, left.Array
-
-            if comparer.Compare(fullArray[rightIdx-1], fullArray[rightIdx]) <= 0 then
-                ()
-            else
-                while leftIdx < rightIdx && rightIdx < rightMax do
-                    if comparer.Compare(fullArray[leftIdx], fullArray[rightIdx]) <= 0 then
-                        leftIdx <- leftIdx + 1
-                    else
-                        let rightValue = fullArray[rightIdx]                 
-                        let mutable whereShouldFirstOfRightGo = rightIdx
-
-                        // Bubble-down the 1st element of right segment to its correct position
-                        while whereShouldFirstOfRightGo <> leftIdx do                          
-                            fullArray[whereShouldFirstOfRightGo] <- fullArray[whereShouldFirstOfRightGo - 1]
-                            whereShouldFirstOfRightGo <- whereShouldFirstOfRightGo - 1
-                       
-                        fullArray[leftIdx] <- rightValue
-
-                        leftIdx <- leftIdx + 1
-                        rightIdx <- rightIdx + 1           
-
-            new ArraySegment<'T>(left.Array, left.Offset, left.Count + right.Count)
-
-        let rec mergeRunsInParallel (segmentsInOrder: ArraySegment<'T>[]) pairwiseMerger =
-            match segmentsInOrder with
-            | [| singleRun |] -> singleRun
-            | [| first; second |] -> pairwiseMerger first second
-            | [||] -> invalidArg "runs" LanguagePrimitives.ErrorStrings.InputArrayEmptyString
-            | threeOrMoreSegments ->
-                let mutable left = None
-                let mutable right = None
-                let midIndex = threeOrMoreSegments.Length / 2
-
-                Parallel.Invoke(
-                    (fun () -> left <- Some(mergeRunsInParallel threeOrMoreSegments[0 .. midIndex - 1] pairwiseMerger)),
-                    (fun () -> right <- Some(mergeRunsInParallel threeOrMoreSegments[midIndex..] pairwiseMerger))
-                )
-
-                pairwiseMerger left.Value right.Value
+        let inline pickPivot ([<InlineIfLambda>]cmpAtIndex:int->int->int) ([<InlineIfLambda>]swapAtIndex:int->int->unit) (orig:ArraySegment<'T>) =
+            let inline swapIfGreater (i:int) (j:int) =                
+                if cmpAtIndex i j > 0 then
+                    swapAtIndex i j
+
+            // Set pivot to be a median of {first,mid,last}
+    
+            let firstIdx = orig.Offset
+            let lastIDx = orig.Offset + orig.Count - 1
+            let midIdx = orig.Offset + orig.Count/2
+    
+            swapIfGreater firstIdx midIdx
+            swapIfGreater firstIdx lastIDx  
+            swapIfGreater midIdx lastIDx      
+            midIdx
+
+        let inline partitionIntoTwo ([<InlineIfLambda>]cmpWithPivot:int->int) ([<InlineIfLambda>]swapAtIndex:int->int->unit) (orig:ArraySegment<'T>) =
+            let mutable leftIdx = orig.Offset+1  // Leftmost is already < pivot
+            let mutable rightIdx = orig.Offset + orig.Count - 2 // Rightmost is already > pivot
+
+            while leftIdx < rightIdx do
+                while cmpWithPivot leftIdx  < 0 do
+                    leftIdx <- leftIdx + 1
+
+                while cmpWithPivot rightIdx > 0 do
+                    rightIdx <- rightIdx - 1
+
+                if leftIdx < rightIdx then
+                    swapAtIndex leftIdx rightIdx     
+                    leftIdx <- leftIdx + 1
+                    rightIdx <- rightIdx - 1
+
+
+            let lastIdx = orig.Offset + orig.Count - 1
+            // There might be more elements being (=)pivot. Exclude them from further work
+            while cmpWithPivot leftIdx  >= 0 && leftIdx>orig.Offset do   
+                leftIdx <- leftIdx - 1
+            while cmpWithPivot rightIdx <= 0 && rightIdx<lastIdx do    
+                rightIdx <- rightIdx + 1
+
+            new ArraySegment<_>(orig.Array, offset=orig.Offset, count=leftIdx - orig.Offset + 1), 
+            new ArraySegment<_>(orig.Array, offset=rightIdx, count=lastIdx - rightIdx + 1)
+
+        let partitionIntoTwoUsingComparer (cmp:'T->'T->int) (orig:ArraySegment<'T>): ArraySegment<'T> * ArraySegment<'T> =
+            let array = orig.Array
+            let inline swap i j =
+                let tmp = array[i]
+                array[i] <- array[j]
+                array[j] <- tmp
+
+            let pivotIdx = pickPivot (fun i j -> cmp array[i] array[j]) (fun i j -> swap i j) orig
+            let pivotItem = array[pivotIdx]
+            partitionIntoTwo (fun idx -> cmp array[idx] pivotItem) (fun i j -> swap i j) orig
+
+
+        let partitionIntoTwoUsingKeys  (keys:'A[]) (orig:ArraySegment<'T>): ArraySegment<'T> * ArraySegment<'T> =
+            let array = orig.Array
+            let inline swap i j = 
+                let tmpKey = keys[i]
+                keys[i] <- keys[j]
+                keys[j] <- tmpKey
+
+                let tmp = array.[i]
+                array.[i] <- array.[j]
+                array.[j] <- tmp
+
+            let pivotIdx = pickPivot (fun i j -> compare keys[i] keys[j]) (fun i j -> swap i j) orig
+            let pivotKey = keys[pivotIdx]
+            partitionIntoTwo (fun idx -> compare keys[idx] pivotKey) (fun i j -> swap i j) orig
+
+        let inline sortInPlaceHelper (array:'T[]) ([<InlineIfLambda>]partitioningFunc:ArraySegment<'T> -> ArraySegment<'T> * ArraySegment<'T>) ([<InlineIfLambda>]sortingFunc:ArraySegment<'T>->unit) =   
+            let rec sortChunk (segment: ArraySegment<_>) freeWorkers =      
+                match freeWorkers with        
+                // Really small arrays are not worth creating a Task for, sort them immediately as well
+                | 0 | 1 | _ when segment.Count <= minChunkSize ->    
+                    sortingFunc segment                    
+                | _ -> 
+                    let left,right = partitioningFunc segment 
+                    // Pivot-based partitions might be inbalanced. Split  free workers for left/right proportional to their size
+                    let itemsPerWorker = Operators.max  ((left.Count + right.Count) / freeWorkers) 1
+                    let workersForLeftTask = 
+                        left.Count / itemsPerWorker 
+                        |> Operators.max 1 
+                        |> Operators.min (freeWorkers-1)                    
+                    let leftTask = Task.Run(fun () -> sortChunk left workersForLeftTask)
+                    sortChunk right (freeWorkers - workersForLeftTask)
+                    leftTask.Wait()  
+        
+            let bigSegment = new ArraySegment<_>(array, 0, array.Length)
+            sortChunk bigSegment maxPartitions
+
+        let sortInPlaceWithHelper (partitioningComparer: 'T->'T->int) (sortingComparer: IComparer<'T>) (inputArray: 'T[]) =   
+            let partitioningFunc = partitionIntoTwoUsingComparer partitioningComparer
+            let sortingFunc = fun (s:ArraySegment<'T>) -> Array.Sort<'T>(inputArray,s.Offset,s.Count,sortingComparer)
+            sortInPlaceHelper inputArray partitioningFunc sortingFunc
+
+        let sortKeysAndValuesInPlace (inputKeys:'TKey[]) (values:'TValue[]) = 
+            let partitioningFunc = partitionIntoTwoUsingKeys inputKeys
+            let sortingComparer = LanguagePrimitives.FastGenericComparerCanBeNull<'TKey>
+            let sortingFunc = fun (s:ArraySegment<'T>) -> Array.Sort<'TKey,'TValue>(inputKeys,values,s.Offset,s.Count,sortingComparer)  
+            sortInPlaceHelper values partitioningFunc sortingFunc
+            
 
         [<CompiledName("SortInPlaceWith")>]
         let sortInPlaceWith comparer (array: 'T[]) =
             checkNonNull "array" array
-            let comparer = ComparisonIdentity.FromFunction(comparer)
-
-            if array.Length < sequentialCutoffForSorting then
-                Array.Sort(array, comparer)
-            else
-                let preSortedPartitions = prepareSortedRunsInPlaceWith array comparer
-
-                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceWith comparer)
-                |> ignore
+            let sortingComparer = ComparisonIdentity.FromFunction(comparer)
+            sortInPlaceWithHelper comparer sortingComparer array          
 
         [<CompiledName("SortInPlaceBy")>]
         let sortInPlaceBy (projection: 'T -> 'U) (array: 'T[]) =
             checkNonNull "array" array
+            let inputKeys : 'U[] = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length 
+            let partitions = createPartitions array
+            Parallel.For(0,partitions.Length, fun i ->
+                let segment = partitions.[i]
+                for idx = segment.Offset to (segment.Offset+segment.Count-1) do                  
+                    inputKeys[idx] <- projection array[idx]) |> ignore
 
-            if array.Length < sequentialCutoffForSorting then
-                Microsoft.FSharp.Primitives.Basics.Array.unstableSortInPlaceBy projection array
-            else
-                let projectedFields = map projection array
-                let preSortedPartitions = prepareSortedRunsInPlace array projectedFields
-
-                mergeRunsInParallel preSortedPartitions (mergeTwoSortedConsequtiveSegmentsInPlaceByKeys projectedFields)
-                |> ignore
+            sortKeysAndValuesInPlace inputKeys array
 
         [<CompiledName("SortInPlace")>]
         let sortInPlace (array: 'T[]) =
             checkNonNull "array" array
 
-            if array.Length < sequentialCutoffForSorting then
-                Microsoft.FSharp.Primitives.Basics.Array.unstableSortInPlace array
-            else
-                let preSortedPartitions =
-                    prepareSortedRunsInPlaceWith array LanguagePrimitives.FastGenericComparerCanBeNull
-
-                mergeRunsInParallel
-                    preSortedPartitions
-                    (mergeTwoSortedConsequtiveSegmentsInPlaceWith LanguagePrimitives.FastGenericComparer)
-                |> ignore
+            let sortingComparer : IComparer<'T> = LanguagePrimitives.FastGenericComparerCanBeNull<'T>
+            let partioningFunc = compare
+            sortInPlaceWithHelper partioningFunc sortingComparer array           
 
         [<CompiledName("SortWith")>]
         let sortWith (comparer: 'T -> 'T -> int) (array: 'T[]) =
@@ -2262,10 +2232,19 @@ module Array =
             result
 
         [<CompiledName("SortBy")>]
-        let sortBy projection array =
-            let result = copy array
-            sortInPlaceBy projection result
-            result
+        let sortBy projection (array: 'T[]) =
+            checkNonNull "array" array
+            let inputKeys = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length 
+            let clone = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked  array.Length  
+            let partitions = createPartitions clone
+            Parallel.For(0,partitions.Length, fun i ->
+                let segment = partitions.[i]
+                for idx = segment.Offset to (segment.Offset+segment.Count-1) do     
+                    clone[idx] <- array[idx]
+                    inputKeys.[idx] <- projection array[idx]) |> ignore
+
+            sortKeysAndValuesInPlace inputKeys clone
+            clone
 
         [<CompiledName("Sort")>]
         let sort array =

From c1a03dcd48d7af4bc6ebf85933a825ae61110289 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 13 Mar 2023 17:51:51 +0100
Subject: [PATCH 08/13] Pivot based impl unified

---
 src/FSharp.Core/array.fs                      | 188 ++++++++++++------
 .../CollectionModulesConsistency.fs           |  10 +-
 2 files changed, 131 insertions(+), 67 deletions(-)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index 1336d6b8462..b5aa32d38d4 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2075,7 +2075,7 @@ module Array =
 
         // The following two parameters were benchmarked and found to be optimal.
         // Benchmark was run using: 11th Gen Intel Core i9-11950H 2.60GHz, 1 CPU, 16 logical and 8 physical cores
-        let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use  
+        let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use
         let private minChunkSize = 256 // The minimum size of a chunk to be sorted in parallel
 
         let private createPartitions (array: 'T[]) =
@@ -2088,72 +2088,85 @@ module Array =
 
                 let mutable offset = 0
 
-                while (offset + chunkSize) <= array.Length do
+                while (offset + chunkSize) < array.Length do
                     yield new ArraySegment<'T>(array, offset, chunkSize)
                     offset <- offset + chunkSize
 
-                if (offset <> array.Length) then
-                    yield new ArraySegment<'T>(array, offset, array.Length - offset)
+                yield new ArraySegment<'T>(array, offset, array.Length - offset)
             |]
 
-        let inline pickPivot ([<InlineIfLambda>]cmpAtIndex:int->int->int) ([<InlineIfLambda>]swapAtIndex:int->int->unit) (orig:ArraySegment<'T>) =
-            let inline swapIfGreater (i:int) (j:int) =                
+        let inline pickPivot
+            ([<InlineIfLambda>] cmpAtIndex: int -> int -> int)
+            ([<InlineIfLambda>] swapAtIndex: int -> int -> unit)
+            (orig: ArraySegment<'T>)
+            =
+            let inline swapIfGreater (i: int) (j: int) =
                 if cmpAtIndex i j > 0 then
                     swapAtIndex i j
 
             // Set pivot to be a median of {first,mid,last}
-    
+
             let firstIdx = orig.Offset
             let lastIDx = orig.Offset + orig.Count - 1
-            let midIdx = orig.Offset + orig.Count/2
-    
+            let midIdx = orig.Offset + orig.Count / 2
+
             swapIfGreater firstIdx midIdx
-            swapIfGreater firstIdx lastIDx  
-            swapIfGreater midIdx lastIDx      
+            swapIfGreater firstIdx lastIDx
+            swapIfGreater midIdx lastIDx
             midIdx
 
-        let inline partitionIntoTwo ([<InlineIfLambda>]cmpWithPivot:int->int) ([<InlineIfLambda>]swapAtIndex:int->int->unit) (orig:ArraySegment<'T>) =
-            let mutable leftIdx = orig.Offset+1  // Leftmost is already < pivot
+        let inline partitionIntoTwo
+            ([<InlineIfLambda>] cmpWithPivot: int -> int)
+            ([<InlineIfLambda>] swapAtIndex: int -> int -> unit)
+            (orig: ArraySegment<'T>)
+            =
+            let mutable leftIdx = orig.Offset + 1 // Leftmost is already < pivot
             let mutable rightIdx = orig.Offset + orig.Count - 2 // Rightmost is already > pivot
 
             while leftIdx < rightIdx do
-                while cmpWithPivot leftIdx  < 0 do
+                while cmpWithPivot leftIdx < 0 do
                     leftIdx <- leftIdx + 1
 
                 while cmpWithPivot rightIdx > 0 do
                     rightIdx <- rightIdx - 1
 
                 if leftIdx < rightIdx then
-                    swapAtIndex leftIdx rightIdx     
+                    swapAtIndex leftIdx rightIdx
                     leftIdx <- leftIdx + 1
                     rightIdx <- rightIdx - 1
 
-
             let lastIdx = orig.Offset + orig.Count - 1
             // There might be more elements being (=)pivot. Exclude them from further work
-            while cmpWithPivot leftIdx  >= 0 && leftIdx>orig.Offset do   
+            while cmpWithPivot leftIdx >= 0 && leftIdx > orig.Offset do
                 leftIdx <- leftIdx - 1
-            while cmpWithPivot rightIdx <= 0 && rightIdx<lastIdx do    
+
+            while cmpWithPivot rightIdx <= 0 && rightIdx < lastIdx do
                 rightIdx <- rightIdx + 1
 
-            new ArraySegment<_>(orig.Array, offset=orig.Offset, count=leftIdx - orig.Offset + 1), 
-            new ArraySegment<_>(orig.Array, offset=rightIdx, count=lastIdx - rightIdx + 1)
+            new ArraySegment<_>(orig.Array, offset = orig.Offset, count = leftIdx - orig.Offset + 1),
+            new ArraySegment<_>(orig.Array, offset = rightIdx, count = lastIdx - rightIdx + 1)
 
-        let partitionIntoTwoUsingComparer (cmp:'T->'T->int) (orig:ArraySegment<'T>): ArraySegment<'T> * ArraySegment<'T> =
+        let partitionIntoTwoUsingComparer
+            (cmp: 'T -> 'T -> int)
+            (orig: ArraySegment<'T>)
+            : ArraySegment<'T> * ArraySegment<'T> =
             let array = orig.Array
+
             let inline swap i j =
                 let tmp = array[i]
                 array[i] <- array[j]
                 array[j] <- tmp
 
-            let pivotIdx = pickPivot (fun i j -> cmp array[i] array[j]) (fun i j -> swap i j) orig
+            let pivotIdx =
+                pickPivot (fun i j -> cmp array[i] array[j]) (fun i j -> swap i j) orig
+
             let pivotItem = array[pivotIdx]
             partitionIntoTwo (fun idx -> cmp array[idx] pivotItem) (fun i j -> swap i j) orig
 
-
-        let partitionIntoTwoUsingKeys  (keys:'A[]) (orig:ArraySegment<'T>): ArraySegment<'T> * ArraySegment<'T> =
+        let partitionIntoTwoUsingKeys (keys: 'A[]) (orig: ArraySegment<'T>) : ArraySegment<'T> * ArraySegment<'T> =
             let array = orig.Array
-            let inline swap i j = 
+
+            let inline swap i j =
                 let tmpKey = keys[i]
                 keys[i] <- keys[j]
                 keys[j] <- tmpKey
@@ -2162,58 +2175,93 @@ module Array =
                 array.[i] <- array.[j]
                 array.[j] <- tmp
 
-            let pivotIdx = pickPivot (fun i j -> compare keys[i] keys[j]) (fun i j -> swap i j) orig
+            let pivotIdx =
+                pickPivot (fun i j -> compare keys[i] keys[j]) (fun i j -> swap i j) orig
+
             let pivotKey = keys[pivotIdx]
             partitionIntoTwo (fun idx -> compare keys[idx] pivotKey) (fun i j -> swap i j) orig
 
-        let inline sortInPlaceHelper (array:'T[]) ([<InlineIfLambda>]partitioningFunc:ArraySegment<'T> -> ArraySegment<'T> * ArraySegment<'T>) ([<InlineIfLambda>]sortingFunc:ArraySegment<'T>->unit) =   
-            let rec sortChunk (segment: ArraySegment<_>) freeWorkers =      
-                match freeWorkers with        
+        let inline sortInPlaceHelper
+            (array: 'T[])
+            ([<InlineIfLambda>] partitioningFunc: ArraySegment<'T> -> ArraySegment<'T> * ArraySegment<'T>)
+            ([<InlineIfLambda>] sortingFunc: ArraySegment<'T> -> unit)
+            =
+            let rec sortChunk (segment: ArraySegment<_>) freeWorkers =
+                match freeWorkers with
                 // Really small arrays are not worth creating a Task for, sort them immediately as well
-                | 0 | 1 | _ when segment.Count <= minChunkSize ->    
-                    sortingFunc segment                    
-                | _ -> 
-                    let left,right = partitioningFunc segment 
-                    // Pivot-based partitions might be inbalanced. Split  free workers for left/right proportional to their size
-                    let itemsPerWorker = Operators.max  ((left.Count + right.Count) / freeWorkers) 1
-                    let workersForLeftTask = 
-                        left.Count / itemsPerWorker 
-                        |> Operators.max 1 
-                        |> Operators.min (freeWorkers-1)                    
-                    let leftTask = Task.Run(fun () -> sortChunk left workersForLeftTask)
-                    sortChunk right (freeWorkers - workersForLeftTask)
-                    leftTask.Wait()  
-        
+                | 0
+                | 1 -> sortingFunc segment
+                | _ when segment.Count <= minChunkSize -> sortingFunc segment
+                | _ ->
+                    let left, right = partitioningFunc segment
+                    // If either of the two is too small, sort small segments straight away.
+                    // If the other happens to be big, leave it with all workes in it's recursive step
+                    if left.Count <= minChunkSize || right.Count <= minChunkSize then
+                        sortChunk left freeWorkers
+                        sortChunk right freeWorkers
+                    else
+                        // Pivot-based partitions might be inbalanced. Split  free workers for left/right proportional to their size
+                        let itemsPerWorker = Operators.max ((left.Count + right.Count) / freeWorkers) 1
+
+                        let workersForLeftTask =
+                            (left.Count / itemsPerWorker)
+                            |> Operators.max 1
+                            |> Operators.min (freeWorkers - 1)
+
+                        let leftTask = Task.Run(fun () -> sortChunk left workersForLeftTask)
+                        sortChunk right (freeWorkers - workersForLeftTask)
+                        leftTask.Wait()
+
             let bigSegment = new ArraySegment<_>(array, 0, array.Length)
             sortChunk bigSegment maxPartitions
 
-        let sortInPlaceWithHelper (partitioningComparer: 'T->'T->int) (sortingComparer: IComparer<'T>) (inputArray: 'T[]) =   
+        let sortInPlaceWithHelper
+            (partitioningComparer: 'T -> 'T -> int)
+            (sortingComparer: IComparer<'T>)
+            (inputArray: 'T[])
+            =
             let partitioningFunc = partitionIntoTwoUsingComparer partitioningComparer
-            let sortingFunc = fun (s:ArraySegment<'T>) -> Array.Sort<'T>(inputArray,s.Offset,s.Count,sortingComparer)
+
+            let sortingFunc =
+                fun (s: ArraySegment<'T>) -> Array.Sort<'T>(inputArray, s.Offset, s.Count, sortingComparer)
+
             sortInPlaceHelper inputArray partitioningFunc sortingFunc
 
-        let sortKeysAndValuesInPlace (inputKeys:'TKey[]) (values:'TValue[]) = 
+        let sortKeysAndValuesInPlace (inputKeys: 'TKey[]) (values: 'TValue[]) =
             let partitioningFunc = partitionIntoTwoUsingKeys inputKeys
             let sortingComparer = LanguagePrimitives.FastGenericComparerCanBeNull<'TKey>
-            let sortingFunc = fun (s:ArraySegment<'T>) -> Array.Sort<'TKey,'TValue>(inputKeys,values,s.Offset,s.Count,sortingComparer)  
+
+            let sortingFunc =
+                fun (s: ArraySegment<'T>) ->
+                    Array.Sort<'TKey, 'TValue>(inputKeys, values, s.Offset, s.Count, sortingComparer)
+
             sortInPlaceHelper values partitioningFunc sortingFunc
-            
 
         [<CompiledName("SortInPlaceWith")>]
         let sortInPlaceWith comparer (array: 'T[]) =
             checkNonNull "array" array
             let sortingComparer = ComparisonIdentity.FromFunction(comparer)
-            sortInPlaceWithHelper comparer sortingComparer array          
+            sortInPlaceWithHelper comparer sortingComparer array
 
         [<CompiledName("SortInPlaceBy")>]
         let sortInPlaceBy (projection: 'T -> 'U) (array: 'T[]) =
             checkNonNull "array" array
-            let inputKeys : 'U[] = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length 
+
+            let inputKeys: 'U[] =
+                Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length
+
             let partitions = createPartitions array
-            Parallel.For(0,partitions.Length, fun i ->
-                let segment = partitions.[i]
-                for idx = segment.Offset to (segment.Offset+segment.Count-1) do                  
-                    inputKeys[idx] <- projection array[idx]) |> ignore
+
+            Parallel.For(
+                0,
+                partitions.Length,
+                fun i ->
+                    let segment = partitions.[i]
+
+                    for idx = segment.Offset to (segment.Offset + segment.Count - 1) do
+                        inputKeys[idx] <- projection array[idx]
+            )
+            |> ignore
 
             sortKeysAndValuesInPlace inputKeys array
 
@@ -2221,9 +2269,11 @@ module Array =
         let sortInPlace (array: 'T[]) =
             checkNonNull "array" array
 
-            let sortingComparer : IComparer<'T> = LanguagePrimitives.FastGenericComparerCanBeNull<'T>
+            let sortingComparer: IComparer<'T> =
+                LanguagePrimitives.FastGenericComparerCanBeNull<'T>
+
             let partioningFunc = compare
-            sortInPlaceWithHelper partioningFunc sortingComparer array           
+            sortInPlaceWithHelper partioningFunc sortingComparer array
 
         [<CompiledName("SortWith")>]
         let sortWith (comparer: 'T -> 'T -> int) (array: 'T[]) =
@@ -2234,14 +2284,26 @@ module Array =
         [<CompiledName("SortBy")>]
         let sortBy projection (array: 'T[]) =
             checkNonNull "array" array
-            let inputKeys = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length 
-            let clone = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked  array.Length  
+
+            let inputKeys =
+                Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length
+
+            let clone =
+                Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length
+
             let partitions = createPartitions clone
-            Parallel.For(0,partitions.Length, fun i ->
-                let segment = partitions.[i]
-                for idx = segment.Offset to (segment.Offset+segment.Count-1) do     
-                    clone[idx] <- array[idx]
-                    inputKeys.[idx] <- projection array[idx]) |> ignore
+
+            Parallel.For(
+                0,
+                partitions.Length,
+                fun i ->
+                    let segment = partitions.[i]
+
+                    for idx = segment.Offset to (segment.Offset + segment.Count - 1) do
+                        clone[idx] <- array[idx]
+                        inputKeys.[idx] <- projection array[idx]
+            )
+            |> ignore
 
             sortKeysAndValuesInPlace inputKeys clone
             clone
diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
index 0ec1ceaadcb..5f7d1d565bb 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs
@@ -8,7 +8,7 @@ open FsCheck
 open Utils
 
 let smallerSizeCheck testable = Check.One({ Config.QuickThrowOnFailure with EndSize = 25 }, testable)
-let bigSizeCheck testable = Check.One({ Config.QuickThrowOnFailure with StartSize = 4_096;EndSize = 30_000 }, testable)
+let bigSizeCheck testable = Check.One({ Config.QuickThrowOnFailure with StartSize = 222;EndSize = 999; MaxTest = 8 }, testable)
 
 /// helper function that creates labeled FsCheck properties for equality comparisons
 let consistency name sqs ls arr =
@@ -1314,7 +1314,7 @@ module ArrayParallelVsArray =
         let pa = xs |> Array.Parallel.sort
 
         let opName = "sort"
-        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a opName pa)
+        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.Parallel.%s = '%A'" opName a opName pa)
 
     [<Fact>]
     let ``sort is consistent`` () =
@@ -1365,7 +1365,7 @@ module ArrayParallelVsArray =
         let a = xs |> Array.sortDescending
         let pa = xs |> Array.Parallel.sortDescending
         let opName = "sortDescending"
-        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.%s = '%A'" opName a opName pa)
+        (a = pa) |@ (sprintf  "Array.%s = '%A', Array.Parallel.%s = '%A'" opName a opName pa)
 
     [<Fact>]
     let ``sortDescending is consistent`` () =
@@ -1377,7 +1377,9 @@ module ArrayParallelVsArray =
         let a = xs |> Array.sortByDescending f
         let pa = xs |> Array.Parallel.sortByDescending f
 
-        isSorted (Array.map f pa |> Array.rev) && isSorted (Array.map f a |> Array.rev) &&
+        let isDescSorted arr = arr |> Array.pairwise |> Array.forall (fun (a,b) -> f a >= f b || a = b)
+
+        isDescSorted a && isDescSorted pa &&
           haveSameElements pa xs && haveSameElements a xs &&
           a.Length = pa.Length && a.Length = xs.Length
 

From c2cf9c692491b76859a067182497465572d47978 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 13 Mar 2023 18:06:08 +0100
Subject: [PATCH 09/13] Fix build

---
 .../FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
index 4a36cd1410d..313c151fb03 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
@@ -649,7 +649,7 @@ type ArrayModule2() =
     member private _.MultiplyArray(template:_[],repetitions:int) = 
         Array.zeroCreate repetitions |> Array.collect (fun _ -> template)     
 
-    member private _.CompareTwoMethods<'TIn,'TOut> (regularArrayFunc:'TIn[]->'TOut[]) (arrayParaFunc:'TIn[]->'TOut[]) (initialData:'TIn[]) = 
+    member private _.CompareTwoMethods<'TIn,'TOut when 'TOut: equality> (regularArrayFunc:'TIn[]->'TOut[]) (arrayParaFunc:'TIn[]->'TOut[]) (initialData:'TIn[]) = 
         let first,second = initialData, Array.copy initialData
         let whenSequential = regularArrayFunc second
         let whenParallel = arrayParaFunc first

From 11ffdbb507921fd9a94b2ebfd233a5368a9416cd Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 13 Mar 2023 18:34:35 +0100
Subject: [PATCH 10/13] Changing descending sorts to do regular sort + then
 reverse in place

---
 src/FSharp.Core/array.fs  | 39 +++++++++++++++++++++++++--------------
 src/FSharp.Core/array.fsi |  4 ++--
 2 files changed, 27 insertions(+), 16 deletions(-)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index b5aa32d38d4..183a7b40ec4 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2078,23 +2078,25 @@ module Array =
         let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use
         let private minChunkSize = 256 // The minimum size of a chunk to be sorted in parallel
 
-        let private createPartitions (array: 'T[]) =
+        let private createPartitionsUpTo maxIdxExclusive (array: 'T[]) =
             [|
                 let chunkSize =
-                    match array.Length with
+                    match maxIdxExclusive with
                     | smallSize when smallSize < minChunkSize -> smallSize
                     | biggerSize when biggerSize % maxPartitions = 0 -> biggerSize / maxPartitions
                     | biggerSize -> (biggerSize / maxPartitions) + 1
 
                 let mutable offset = 0
 
-                while (offset + chunkSize) < array.Length do
+                while (offset + chunkSize) < maxIdxExclusive do
                     yield new ArraySegment<'T>(array, offset, chunkSize)
                     offset <- offset + chunkSize
 
-                yield new ArraySegment<'T>(array, offset, array.Length - offset)
+                yield new ArraySegment<'T>(array, offset, maxIdxExclusive - offset)
             |]
 
+        let private createPartitions (array: 'T[]) = createPartitionsUpTo array.Length array         
+
         let inline pickPivot
             ([<InlineIfLambda>] cmpAtIndex: int -> int -> int)
             ([<InlineIfLambda>] swapAtIndex: int -> int -> unit)
@@ -2314,16 +2316,25 @@ module Array =
             sortInPlace result
             result
 
-        [<CompiledName("SortByDescending")>]
-        let inline sortByDescending projection array =
-            let inline compareDescending a b =
-                compare (projection b) (projection a)
+        let reverseInPlace (array:'T[]) =          
+            let segments = createPartitionsUpTo (array.Length/2) array  
+            let lastIdx = array.Length - 1
+            Parallel.For(0,segments.Length, fun idx ->
+                let s = segments[idx]
+                for i=s.Offset to (s.Offset+s.Count-1) do
+                    let tmp = array[i]
+                    array[i] <- array[lastIdx-i]
+                    array[lastIdx-i] <- tmp ) |> ignore
+            array
 
-            sortWith compareDescending array
+        [<CompiledName("SortByDescending")>]
+        let sortByDescending projection array =
+            array
+            |> sortBy projection
+            |> reverseInPlace
 
         [<CompiledName("SortDescending")>]
-        let inline sortDescending array =
-            let inline compareDescending a b =
-                compare b a
-
-            sortWith compareDescending array
+        let sortDescending array =
+            array
+            |> sort 
+            |> reverseInPlace
diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi
index c9c00cf91d9..a6adbfc9558 100644
--- a/src/FSharp.Core/array.fsi
+++ b/src/FSharp.Core/array.fsi
@@ -3471,7 +3471,7 @@ module Array =
         /// </example>
         [<CompiledName("SortDescending")>]
         [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
-        val inline sortDescending: array:'T[] -> 'T[] when 'T : comparison
+        val sortDescending: array:'T[] -> 'T[] when 'T : comparison
 
         /// <summary>Sorts the elements of an array in parallel, in descending order, using the given projection for the keys and returning a new array. 
         /// Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare"/>.</summary>
@@ -3494,4 +3494,4 @@ module Array =
         /// </example>
         [<CompiledName("SortByDescending")>]
         [<Experimental("Experimental library feature, requires '--langversion:preview'")>]
-        val inline sortByDescending: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison
+        val sortByDescending: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison

From 52580bcabd38e09c8fd8a037cf77c87f2bad841d Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 13 Mar 2023 18:40:56 +0100
Subject: [PATCH 11/13] fantomas'd

---
 src/FSharp.Core/array.fs | 35 ++++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index 183a7b40ec4..9e8f4332db2 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2095,7 +2095,8 @@ module Array =
                 yield new ArraySegment<'T>(array, offset, maxIdxExclusive - offset)
             |]
 
-        let private createPartitions (array: 'T[]) = createPartitionsUpTo array.Length array         
+        let private createPartitions (array: 'T[]) =
+            createPartitionsUpTo array.Length array
 
         let inline pickPivot
             ([<InlineIfLambda>] cmpAtIndex: int -> int -> int)
@@ -2316,25 +2317,29 @@ module Array =
             sortInPlace result
             result
 
-        let reverseInPlace (array:'T[]) =          
-            let segments = createPartitionsUpTo (array.Length/2) array  
+        let reverseInPlace (array: 'T[]) =
+            let segments = createPartitionsUpTo (array.Length / 2) array
             let lastIdx = array.Length - 1
-            Parallel.For(0,segments.Length, fun idx ->
-                let s = segments[idx]
-                for i=s.Offset to (s.Offset+s.Count-1) do
-                    let tmp = array[i]
-                    array[i] <- array[lastIdx-i]
-                    array[lastIdx-i] <- tmp ) |> ignore
+
+            Parallel.For(
+                0,
+                segments.Length,
+                fun idx ->
+                    let s = segments[idx]
+
+                    for i = s.Offset to (s.Offset + s.Count - 1) do
+                        let tmp = array[i]
+                        array[i] <- array[lastIdx - i]
+                        array[lastIdx - i] <- tmp
+            )
+            |> ignore
+
             array
 
         [<CompiledName("SortByDescending")>]
         let sortByDescending projection array =
-            array
-            |> sortBy projection
-            |> reverseInPlace
+            array |> sortBy projection |> reverseInPlace
 
         [<CompiledName("SortDescending")>]
         let sortDescending array =
-            array
-            |> sort 
-            |> reverseInPlace
+            array |> sort |> reverseInPlace

From 3c322e83f68da5c23569aba799b8b6d9124a12d8 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 20 Mar 2023 11:32:06 +0100
Subject: [PATCH 12/13] Update
 tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs

Co-authored-by: Petr <psfinaki@users.noreply.github.com>
---
 .../FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
index 313c151fb03..737badcbb9e 100644
--- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
+++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs
@@ -793,7 +793,7 @@ type ArrayModule2() =
         this.MultiplyArray([|3;5;7;2;4;8|],1_000) 
         |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending)
         
-        // string Array
+        // string array
         this.MultiplyArray([|"Z";"a";"d"; ""; "Y"; null; "c";"b";"X"|]  ,1_000) 
         |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending)
         

From 58d1e04df84e4f11bd2ce067d209185b9da39fca Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 20 Mar 2023 19:52:39 +0100
Subject: [PATCH 13/13] Remove code which was added in another PR and is
 already merged

---
 src/FSharp.Core/array.fs | 22 ----------------------
 1 file changed, 22 deletions(-)

diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs
index b1f373dc598..1fe869cd4b2 100644
--- a/src/FSharp.Core/array.fs
+++ b/src/FSharp.Core/array.fs
@@ -2235,28 +2235,6 @@ module Array =
 
             res1, res2
 
-        // The following two parameters were benchmarked and found to be optimal.
-        // Benchmark was run using: 11th Gen Intel Core i9-11950H 2.60GHz, 1 CPU, 16 logical and 8 physical cores
-        let private maxPartitions = Environment.ProcessorCount // The maximum number of partitions to use
-        let private minChunkSize = 256 // The minimum size of a chunk to be sorted in parallel
-
-        let private createPartitionsUpTo maxIdxExclusive (array: 'T[]) =
-            [|
-                let chunkSize =
-                    match maxIdxExclusive with
-                    | smallSize when smallSize < minChunkSize -> smallSize
-                    | biggerSize when biggerSize % maxPartitions = 0 -> biggerSize / maxPartitions
-                    | biggerSize -> (biggerSize / maxPartitions) + 1
-
-                let mutable offset = 0
-
-                while (offset + chunkSize) < maxIdxExclusive do
-                    yield new ArraySegment<'T>(array, offset, chunkSize)
-                    offset <- offset + chunkSize
-
-                yield new ArraySegment<'T>(array, offset, maxIdxExclusive - offset)
-            |]
-
         let private createPartitions (array: 'T[]) =
             createPartitionsUpTo array.Length array