diff --git a/docs/release-notes/.FSharp.Core/10.0.100.md b/docs/release-notes/.FSharp.Core/10.0.100.md index 10ff967c125..9d348a8663c 100644 --- a/docs/release-notes/.FSharp.Core/10.0.100.md +++ b/docs/release-notes/.FSharp.Core/10.0.100.md @@ -7,5 +7,8 @@ ### Changed * Random functions support for zero element chosen/sampled ([PR #18568](https://github.com/dotnet/fsharp/pull/18568)) +* Optimize array slicing performance. ([PR #18778](https://github.com/dotnet/fsharp/pull/18778)) ### Breaking Changes + +* 1D array slicing now returns an empty array singleton instead of allocating a new array when the result is empty. ([PR #18778](https://github.com/dotnet/fsharp/pull/18778)) \ No newline at end of file diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index 0fb2b87a06d..6b43c445338 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -779,17 +779,17 @@ namespace Microsoft.FSharp.Core let inline SetArray (target: 'T array) (index:int) (value:'T) = (# "stelem.any !0" type ('T) target index value #) - let inline GetArraySub arr (start:int) (len:int) = - let len = if len < 0 then 0 else len - let dst = zeroCreate len - for i = 0 to len - 1 do - SetArray dst i (GetArray arr (start + i)) - dst - - let inline SetArraySub arr (start:int) (len:int) (src:_ array) = - for i = 0 to len - 1 do - SetArray arr (start+i) (GetArray src i) + let inline GetArraySub (arr: 'a array) (start:int) (len:int) : 'a array = + if len <= 0 then + [||] + else + let dst = zeroCreate len + Array.Copy(arr, start, dst, 0, len) + dst + let inline SetArraySub (arr: 'T array) (start:int) (len:int) (src: 'T array) = + if len > 0 then + Array.Copy(src, 0, arr, start, len) let inline GetArray2D (source: 'T[,]) (index1: int) (index2: int) = (# "ldelem.multi 2 !0" type ('T) source index1 index2 : 'T #) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs index 16bc5d4fbdf..e09ce1758c7 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs @@ -1962,6 +1962,24 @@ type ArrayModule() = Assert.AreEqual(arr.[-4..(-3)], ([||]: int array)) + [] + member _.``Slicing creates new non-empty arrays`` () = + let arr = [|1;2;3;4;5;6|] + + Assert.False(Object.ReferenceEquals(arr[1..3], arr[1..3])) + Assert.False(Object.ReferenceEquals(arr[0..], arr[0..])) + Assert.False(Object.ReferenceEquals(arr[..^1], arr[..^1])) + + [] + member _.``Empty slices are referentially equivalent`` () = + let arr = [|1;2;3;4;5;6|] + + Assert.AreEqual(arr[1..-1].Length, 0) + Assert.True(Object.ReferenceEquals(arr[1..-1], arr[1..-1])) + + Assert.True(Object.ReferenceEquals(arr[1..-1], ([||]: int array))) + + [] member _.RandomShuffle() = let arr = [| 1..20 |] diff --git a/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/ArraySlicing.fs b/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/ArraySlicing.fs new file mode 100644 index 00000000000..5d08be22c52 --- /dev/null +++ b/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/ArraySlicing.fs @@ -0,0 +1,15 @@ +module ArraySlicing + +open BenchmarkDotNet.Attributes +open System + +[] +[] +[] +[] +type ArraySlicingBenchmark() = + let b = [| for _ in 1 .. 100_000 -> byte DateTime.Now.Ticks |] + + [] + member _.x () = + b[ 20 .. 4999 ] \ No newline at end of file diff --git a/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/MicroPerf.fsproj b/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/MicroPerf.fsproj index 2830bcffe41..72a9aa08670 100644 --- a/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/MicroPerf.fsproj +++ b/tests/benchmarks/CompiledCodeBenchmarks/MicroPerf/MicroPerf.fsproj @@ -26,6 +26,7 @@ +