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 <> 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 <> 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