diff --git a/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs b/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs index ef85efa11ee48..0abaab4156bb8 100644 --- a/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs +++ b/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; @@ -51,30 +52,44 @@ public void Dispose() int segmentsCount = _segmentsCount; if (segmentsCount != 0) { - ReadOnlySpan segments = _segments; + ReturnArrays(segmentsCount); + } + } - // We need to return all rented arrays to the pool, and if the arrays contain any references, - // we want to clear them first so that the pool doesn't artificially root contained objects. - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - // Return all but the last segment. All of these are full and need to be entirely cleared. - foreach (T[] segment in segments.Slice(0, segmentsCount - 1)) - { - ArrayPool.Shared.Return(segment, clearArray: true); - } + private void ReturnArrays(int segmentsCount) + { + Debug.Assert(segmentsCount > 0); + ReadOnlySpan segments = _segments; - // For the last segment, we can clear only what we know was used. - T[] currentSegment = segments[segmentsCount - 1]; - Array.Clear(currentSegment, 0, _countInCurrentSegment); - ArrayPool.Shared.Return(currentSegment); + // We need to return all rented arrays to the pool, and if the arrays contain any references, + // we want to clear them first so that the pool doesn't artificially root contained objects. + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + // Return all but the last segment. All of these are full and need to be entirely cleared. + segmentsCount--; + foreach (T[] segment in segments.Slice(0, segmentsCount)) + { + Array.Clear(segment); + ArrayPool.Shared.Return(segment); } - else + + // For the last segment, we can clear only what we know was used. + T[] currentSegment = segments[segmentsCount]; + Array.Clear(currentSegment, 0, _countInCurrentSegment); + ArrayPool.Shared.Return(currentSegment); + } + else + { + // Return every rented array without clearing. + for (int i = 0; i < segments.Length; i++) { - // Return every rented array without clearing. - foreach (T[] segment in segments.Slice(0, segmentsCount)) + T[] segment = segments[i]; + if (segment is null) { - ArrayPool.Shared.Return(segment); + break; } + + ArrayPool.Shared.Return(segment); } } } @@ -193,7 +208,7 @@ public void AddNonICollectionRange(IEnumerable source) => /// and ICollection and thus doesn't bother checking to see if it is. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddNonICollectionRangeInlined(IEnumerable source) + internal void AddNonICollectionRangeInlined(IEnumerable source) { Span currentSegment = _currentSegment; int countInCurrentSegment = _countInCurrentSegment; @@ -218,16 +233,39 @@ private void AddNonICollectionRangeInlined(IEnumerable source) } /// Creates an array containing all of the elements in the builder. - /// The number of extra elements of room to allocate in the resulting array. - public T[] ToArray(int additionalLength = 0) + public readonly T[] ToArray() { - T[] result = []; + T[] result; + int count = Count; + if (count != 0) + { + result = GC.AllocateUninitializedArray(count); + ToSpanInlined(result); + } + else + { + result = []; + } + + return result; + } + + /// Creates an array containing all of the elements in the builder. + /// The number of extra elements of room to allocate in the resulting array. + public readonly T[] ToArray(int additionalLength) + { + T[] result; int count = checked(Count + additionalLength); + if (count != 0) { result = GC.AllocateUninitializedArray(count); - ToSpan(result); + ToSpanInlined(result); + } + else + { + result = []; } return result; @@ -235,7 +273,13 @@ public T[] ToArray(int additionalLength = 0) /// Populates the destination span with all of the elements in the builder. /// The destination span. - public void ToSpan(Span destination) + [MethodImpl(MethodImplOptions.NoInlining)] + public readonly void ToSpan(Span destination) => ToSpanInlined(destination); + + /// Populates the destination span with all of the elements in the builder. + /// The destination span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly void ToSpanInlined(Span destination) { int segmentsCount = _segmentsCount; if (segmentsCount != 0) @@ -247,11 +291,14 @@ public void ToSpan(Span destination) // Copy the 0..N-1 segments segmentsCount--; - foreach (T[] arr in ((ReadOnlySpan)_segments).Slice(0, segmentsCount)) + if (segmentsCount != 0) { - ReadOnlySpan segment = arr; - segment.CopyTo(destination); - destination = destination.Slice(segment.Length); + foreach (T[] arr in ((ReadOnlySpan)_segments).Slice(0, segmentsCount)) + { + ReadOnlySpan segment = arr; + segment.CopyTo(destination); + destination = destination.Slice(segment.Length); + } } } diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index 8b2ad651d1d2b..043cac8f0038b 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace System.Linq @@ -33,12 +33,16 @@ public static TSource[] ToArray(this IEnumerable source) return []; } - else + + return EnumerableToArray(source); + + [MethodImpl(MethodImplOptions.NoInlining)] // avoid large stack allocation impacting other paths + static TSource[] EnumerableToArray(IEnumerable source) { SegmentedArrayBuilder.ScratchBuffer scratch = default; SegmentedArrayBuilder builder = new(scratch); - builder.AddNonICollectionRange(source); + builder.AddNonICollectionRangeInlined(source); TSource[] result = builder.ToArray(); builder.Dispose();