diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SizeOpt.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SizeOpt.cs new file mode 100644 index 00000000000000..b832ef0cb9cf8d --- /dev/null +++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SizeOpt.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Linq; + +namespace System.Collections.Generic +{ + /// + /// Internal helper functions for working with enumerables. + /// + internal static partial class EnumerableHelpers + { + /// Converts an enumerable to an array. + /// The enumerable to convert. + /// The resulting array. + internal static T[] ToArray(IEnumerable source) + { + Debug.Assert(source != null); + + if (source is ICollection collection) + { + int count = collection.Count; + if (count == 0) + { + return Array.Empty(); + } + + var result = new T[count]; + collection.CopyTo(result, arrayIndex: 0); + return result; + } + + var builder = new LargeArrayBuilder(initialize: true); + builder.AddRange(source); + return builder.ToArray(); + } + } +} diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SpeedOpt.cs new file mode 100644 index 00000000000000..8c64554389ba6e --- /dev/null +++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SpeedOpt.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Linq; + +namespace System.Collections.Generic +{ + /// + /// Internal helper functions for working with enumerables. + /// + internal static partial class EnumerableHelpers + { + /// Converts an enumerable to an array. + /// The enumerable to convert. + /// The resulting array. + internal static T[] ToArray(IEnumerable source) => ToArrayEnumerable.Instance.ToArray(source); + } +} diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs index ea39f90da410fc..691f64e2359548 100644 --- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs +++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs @@ -86,30 +86,5 @@ internal static void IterativeCopy(IEnumerable source, T[] array, int arra Debug.Assert(arrayIndex == endIndex); } - - /// Converts an enumerable to an array. - /// The enumerable to convert. - /// The resulting array. - internal static T[] ToArray(IEnumerable source) - { - Debug.Assert(source != null); - - if (source is ICollection collection) - { - int count = collection.Count; - if (count == 0) - { - return Array.Empty(); - } - - var result = new T[count]; - collection.CopyTo(result, arrayIndex: 0); - return result; - } - - var builder = new LargeArrayBuilder(initialize: true); - builder.AddRange(source); - return builder.ToArray(); - } } } diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs new file mode 100644 index 00000000000000..b8d1865d1ebd7f --- /dev/null +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -0,0 +1,1021 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; + +namespace System.Collections.Generic +{ + internal abstract class ToArrayBase + { + // from https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element + private const int maxByteElementsArraySize = 0X7FFFFFC7; + private const int maxOtherElementsArraySize = 0X7FEFFFFF; + + protected const int maxArraySize = maxByteElementsArraySize; + + private const int initialRecursiveDepth = 16; + private const int itemsPerRecursion = 4; + + protected const int maxSizeForNoAllocations = initialRecursiveDepth * itemsPerRecursion; + + private int GetBufferSize(int count, int sourceSize) + { + if (count >= maxByteElementsArraySize) + { + // We fail here "early" as we could be creating more buffers (which would be smaller thaat the max) + // but would eventually lead to an excessive allocation which would throw an OutOfMemory exception + throw new OutOfMemoryException(); + } + + var maxSize = + count < sourceSize + ? sourceSize + : count < maxOtherElementsArraySize + ? maxOtherElementsArraySize + : maxByteElementsArraySize; + + return (int)Math.Min((uint)maxSize, (uint)count * 2) - count; + } + + protected T[] FinishViaAllocations(object source, int sourceSize, ref int sourceIdx, object? predicate, object? selector, int count) + { + T[] result; + int bufferSize = GetBufferSize(count, sourceSize); + T[] buffer = new T[bufferSize]; + + var (index, moveNext) = PopulateBuffer(buffer, source, ref sourceIdx, predicate, selector); + + if (moveNext) + { + result = FinishViaAllocations(source, sourceSize, ref sourceIdx, predicate, selector, count + index); + } + else + { + result = new T[count + index]; + } + + Array.Copy(buffer, 0, result, count, index); + + return result; + } + + protected abstract (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector); + + protected static T[] Allocate(int count) + { + if (count == 0) + return Array.Empty(); + + return new T[count]; + } + + protected static T[] AllocateAndAssign(int count, T item1) + { + T[] result = new T[count]; + + result[--count] = item1; + + return result; + } + + protected static T[] AllocateAndAssign(int count, T item1, T item2) + { + T[] result = new T[count]; + + result[--count] = item2; + result[--count] = item1; + + return result; + } + + protected static T[] AllocateAndAssign(int count, T item1, T item2, T item3) + { + T[] result = new T[count]; + + result[--count] = item3; + result[--count] = item2; + result[--count] = item1; + + return result; + } + + protected static T[] Assign(T[] result, ref (T, T, T, T) items, int count) + { + result[--count] = items.Item4; + result[--count] = items.Item3; + result[--count] = items.Item2; + result[--count] = items.Item1; + + return result; + } + } + + internal class ToArrayEnumerable : ToArrayBase + { + public static readonly ToArrayEnumerable Instance = new ToArrayEnumerable(); + + private ToArrayEnumerable() { } + + public T[] ToArray(IEnumerable source) + { + Debug.Assert(source != null); + + if (source is ICollection collection) + { + int count = collection.Count; + if (count == 0) + { + return Array.Empty(); + } + + var result = new T[count]; + collection.CopyTo(result, arrayIndex: 0); + return result; + } + + using IEnumerator e = source.GetEnumerator(); + return InitiallyTryWithNoAllocations(e, 0); + } + + public T[] ToArray(IEnumerable source, Func predicate) + { + Debug.Assert(source != null); + using IEnumerator e = source.GetEnumerator(); + return InitiallyTryWithNoAllocations(e, predicate, 0); + } + + private T[] InitiallyTryWithNoAllocations(IEnumerator source, int count) + { + (T, T, T, T) items; + + if (!source.MoveNext()) + { + return Allocate(count); + } + items.Item1 = source.Current; + + ++count; + + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1); + } + items.Item2 = source.Current; + + ++count; + + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + items.Item3 = source.Current; + + ++count; + + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + items.Item4 = source.Current; + + ++count; + + T[] result; + if (count >= maxSizeForNoAllocations) + { + if (source.MoveNext()) + result = FinishViaAllocations(source, count); + else + result = Allocate(count); + } + else + { + result = InitiallyTryWithNoAllocations(source, count); + } + + return Assign(result, ref items, count); + + T[] FinishViaAllocations(IEnumerator source, int count) + { + int dummyIdx = 0; + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, null, null, count); + } + } + + private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func predicate, int count) + { + (T, T, T, T) items; + + do + { + if (!source.MoveNext()) + { + return Allocate(count); + } + items.Item1 = source.Current; + } + while (!predicate(items.Item1)); + + ++count; + + do + { + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1); + } + items.Item2 = source.Current; + } while (!predicate(items.Item2)); + + ++count; + + do + { + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + items.Item3 = source.Current; + } + while (!predicate(items.Item3)); + + ++count; + + do + { + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + items.Item4 = source.Current; + } + while (!predicate(items.Item4)); + + ++count; + + T[] result; + if (count >= maxSizeForNoAllocations) + { + if (source.MoveNext()) + result = FinishViaAllocations(source, predicate, count); + else + result = Allocate(count); + } + else + { + result = InitiallyTryWithNoAllocations(source, predicate, count); + } + + return Assign(result, ref items, count); + + T[] FinishViaAllocations(IEnumerator source, Func? predicate, int count) + { + int dummyIdx = 0; + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, predicate, null, count); + } + } + + protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector) + { + var enumerator = (IEnumerator)source; + + Func typedPredicate = (Func)predicate!; + + return predicate == null ? PopulateBuffer(buffer, enumerator) : PopulateBuffer(buffer, enumerator, typedPredicate); + } + + private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e) + { + bool moveNext; + int index; + + for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext(), ++index) + { + buffer[index] = e.Current; + } + + return (index, moveNext); + } + + private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e, Func predicate) + { + bool moveNext; + int index; + + for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext()) + { + var item = e.Current; + + if (!predicate(item)) + continue; + + buffer[index++] = item; + } + + return (index, moveNext); + } + } + + internal class ToArrayEnumerableWithSelect : ToArrayBase + { + public static readonly ToArrayEnumerableWithSelect Instance = new ToArrayEnumerableWithSelect(); + + private ToArrayEnumerableWithSelect() { } + + public U[] ToArray(IEnumerable source, Func selector) + { + Debug.Assert(source != null); + using IEnumerator e = source.GetEnumerator(); + + if (source is ICollection collection) + { + int count = collection.Count; + if (count == 0) + { + return Array.Empty(); + } + + var result = new U[count]; + + int idx = 0; + bool moveNext; + for (moveNext = e.MoveNext(), idx = 0; moveNext && idx < result.Length; moveNext = e.MoveNext(), ++idx) + { + result[idx] = selector(e.Current); + } + if (moveNext || idx != result.Length) + throw new IndexOutOfRangeException(); + + return result; + } + + return InitiallyTryWithNoAllocations(e, selector, 0); + } + + public U[] ToArray(IEnumerable source, Func predicate, Func selector) + { + Debug.Assert(source != null); + using IEnumerator e = source.GetEnumerator(); + return InitiallyTryWithNoAllocations(e, predicate, selector, 0); + } + + private U[] InitiallyTryWithNoAllocations(IEnumerator source, Func selector, int count) + { + (U, U, U, U) items; + + if (!source.MoveNext()) + { + return Allocate(count); + } + items.Item1 = selector(source.Current); + + ++count; + + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1); + } + items.Item2 = selector(source.Current); + + ++count; + + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + items.Item3 = selector(source.Current); + + ++count; + + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + items.Item4 = selector(source.Current); + + ++count; + + U[] result; + if (count >= maxSizeForNoAllocations) + { + if (source.MoveNext()) + result = FinishViaAllocations(source, selector, count); + else + result = Allocate(count); + } + else + { + result = InitiallyTryWithNoAllocations(source, selector, count); + } + + return Assign(result, ref items, count); + + U[] FinishViaAllocations(IEnumerator source, Func selector, int count) + { + int dummyIdx = 0; + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, null, selector, count); + } + } + + private U[] InitiallyTryWithNoAllocations(IEnumerator source, Func predicate, Func selector, int count) + { + T current; + (U, U, U, U) items; + + do + { + if (!source.MoveNext()) + { + return Allocate(count); + } + current = source.Current; + } + while (!predicate(current)); + + items.Item1 = selector(current); + ++count; + + do + { + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1); + } + current = source.Current; + } while (!predicate(current)); + + items.Item2 = selector(current); + ++count; + + do + { + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + current = source.Current; + } + while (!predicate(current)); + + items.Item3 = selector(current); + ++count; + + do + { + if (!source.MoveNext()) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + current = source.Current; + } + while (!predicate(current)); + + items.Item4 = selector(current); + ++count; + + U[] result; + if (count >= maxSizeForNoAllocations) + { + if (source.MoveNext()) + result = FinishViaAllocations(source, predicate, selector, count); + else + result = Allocate(count); + } + else + { + result = InitiallyTryWithNoAllocations(source, predicate, selector, count); + } + + return Assign(result, ref items, count); + + U[] FinishViaAllocations(IEnumerator source, Func predicate, Func selector, int count) + { + int dummyIdx = 0; + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, predicate, selector, count); + } + } + + protected override (int, bool) PopulateBuffer(U[] buffer, object source, ref int sourceIdx, object? predicate, object? selector) + { + var enumerator = (IEnumerator)source; + + Func typedPredicate = (Func)predicate!; + Func typedSelector = (Func)selector!; + + return predicate == null ? PopulateBuffer(buffer, enumerator, typedSelector) : PopulateBuffer(buffer, enumerator, typedPredicate, typedSelector); + } + + private static (int, bool) PopulateBuffer(U[] buffer, IEnumerator e, Func selector) + { + bool moveNext; + int index; + + for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext(), ++index) + { + buffer[index] = selector(e.Current); + } + + return (index, moveNext); + } + + private static (int, bool) PopulateBuffer(U[] buffer, IEnumerator e, Func predicate, Func selector) + { + bool moveNext; + int index; + + for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext()) + { + var item = e.Current; + + if (!predicate(item)) + continue; + + buffer[index++] = selector(item); + } + + return (index, moveNext); + } + } + + internal class ToArrayArrayWhere : ToArrayBase + { + public static readonly ToArrayArrayWhere Instance = new ToArrayArrayWhere(); + + private ToArrayArrayWhere() { } + + public T[] ToArray(T[] source, Func predicate) + { + Debug.Assert(source != null); + + return InitiallyTryWithNoAllocations(source, 0, predicate, 0); + } + + private T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func predicate, int count) + { + (T, T, T, T) items; + + do + { + if (sourceIdx >= source.Length) + { + return Allocate(count); + } + + items.Item1 = source[sourceIdx++]; + } while (!predicate(items.Item1)); + + ++count; + + do + { + if (sourceIdx >= source.Length) + { + return AllocateAndAssign(count, items.Item1); + } + items.Item2 = source[sourceIdx++]; + } + while (!predicate(items.Item2)); + + ++count; + + do + { + if (sourceIdx >= source.Length) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + items.Item3 = source[sourceIdx++]; + } + while (!predicate(items.Item3)); + + ++count; + + do + { + if (sourceIdx >= source.Length) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + items.Item4 = source[sourceIdx++]; + } + while (!predicate(items.Item4)); + + ++count; + + T[] result; + if (count >= maxSizeForNoAllocations) + { + if (sourceIdx < source.Length) + { + result = FinishViaAllocations(source, source.Length, ref sourceIdx, predicate, null, count); + } + else + { + result = Allocate(count); + } + } + else + { + result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, count); + } + + return Assign(result, ref items, count); + } + + protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector) + { + Debug.Assert(predicate != null); + + T[] array = (T[])source; + Func typedPredicate = (Func)predicate; + + var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate); + + return (bufferIdx, sourceIdx < array.Length); + } + + private static int PopulateBuffer(T[] buffer, T[] source, ref int sourceIdx, Func predicate) + { + int bufferIdx; + int arrayIdx; + + for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Length && bufferIdx < buffer.Length; ++arrayIdx) + { + var item = source[arrayIdx]; + + if (!predicate(item)) + continue; + + buffer[bufferIdx++] = item; + } + + sourceIdx = arrayIdx; + return bufferIdx; + } + } + + internal class ToArrayArrayWhereSelect : ToArrayBase + { + public static readonly ToArrayArrayWhereSelect Instance = new ToArrayArrayWhereSelect(); + + private ToArrayArrayWhereSelect() { } + + public U[] ToArray(T[] source, Func predicate, Func selector) + { + Debug.Assert(source != null); + + return InitiallyTryWithNoAllocations(source, 0, predicate, selector, 0); + } + + private U[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func predicate, Func selector, int count) + { + T current; + (U, U, U, U) items; + + do + { + if (sourceIdx >= source.Length) + { + return Allocate(count); + } + + current = source[sourceIdx++]; + } while (!predicate(current)); + + items.Item1 = selector(current); + ++count; + + do + { + if (sourceIdx >= source.Length) + { + return AllocateAndAssign(count, items.Item1); + } + current = source[sourceIdx++]; + } + while (!predicate(current)); + + items.Item2 = selector(current); + ++count; + + do + { + if (sourceIdx >= source.Length) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + current = source[sourceIdx++]; + } + while (!predicate(current)); + + items.Item3 = selector(current); + ++count; + + do + { + if (sourceIdx >= source.Length) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + current = source[sourceIdx++]; + } + while (!predicate(current)); + + items.Item4 = selector(current); + ++count; + + U[] result; + if (count >= maxSizeForNoAllocations) + { + if (sourceIdx < source.Length) + { + result = FinishViaAllocations(source, source.Length, ref sourceIdx, predicate, selector, count); + } + else + { + result = Allocate(count); + } + } + else + { + result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, selector, count); + } + + return Assign(result, ref items, count); + } + + protected override (int, bool) PopulateBuffer(U[] buffer, object source, ref int sourceIdx, object? predicate, object? selector) + { + Debug.Assert(predicate != null); + + T[] array = (T[])source; + Func typedPredicate = (Func)predicate!; + Func typedSelector = (Func)selector!; + + var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate, typedSelector); + + return (bufferIdx, sourceIdx < array.Length); + } + + private static int PopulateBuffer(U[] buffer, T[] source, ref int sourceIdx, Func predicate, Func selector) + { + int bufferIdx; + int arrayIdx; + + for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Length && bufferIdx < buffer.Length; ++arrayIdx) + { + var item = source[arrayIdx]; + + if (!predicate(item)) + continue; + + buffer[bufferIdx++] = selector(item); + } + + sourceIdx = arrayIdx; + return bufferIdx; + } + } + + internal class ToArrayListWhere : ToArrayBase + { + public static readonly ToArrayListWhere Instance = new ToArrayListWhere(); + + private ToArrayListWhere() { } + + public T[] ToArray(List source, Func predicate) + { + Debug.Assert(source != null); + + return InitiallyTryWithNoAllocations(source, 0, predicate, 0); + } + + private T[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func predicate, int count) + { + (T, T, T, T) items; + + do + { + if (sourceIdx >= source.Count) + { + return Allocate(count); + } + + items.Item1 = source[sourceIdx++]; + } while (!predicate(items.Item1)); + + ++count; + + do + { + if (sourceIdx >= source.Count) + { + return AllocateAndAssign(count, items.Item1); + } + items.Item2 = source[sourceIdx++]; + } + while (!predicate(items.Item2)); + + ++count; + + do + { + if (sourceIdx >= source.Count) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + items.Item3 = source[sourceIdx++]; + } + while (!predicate(items.Item3)); + + ++count; + + do + { + if (sourceIdx >= source.Count) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + items.Item4 = source[sourceIdx++]; + } + while (!predicate(items.Item4)); + + ++count; + + T[] result; + if (count >= maxSizeForNoAllocations) + { + if (sourceIdx < source.Count) + { + result = FinishViaAllocations(source, source.Count, ref sourceIdx, predicate, null, count); + } + else + { + result = Allocate(count); + } + } + else + { + result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, count); + } + + return Assign(result, ref items, count); + } + + protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector) + { + Debug.Assert(predicate != null); + + List array = (List)source; + Func typedPredicate = (Func)predicate; + + var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate); + + return (bufferIdx, sourceIdx < array.Count); + } + + private static int PopulateBuffer(T[] buffer, List source, ref int sourceIdx, Func predicate) + { + int bufferIdx; + int arrayIdx; + + for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Count && bufferIdx < buffer.Length; ++arrayIdx) + { + var item = source[arrayIdx]; + + if (!predicate(item)) + continue; + + buffer[bufferIdx++] = item; + } + + sourceIdx = arrayIdx; + return bufferIdx; + } + } + + internal class ToArrayListWhereSelect : ToArrayBase + { + public static readonly ToArrayListWhereSelect Instance = new ToArrayListWhereSelect(); + + private ToArrayListWhereSelect() { } + + public U[] ToArray(List source, Func predicate, Func selector) + { + Debug.Assert(source != null); + + return InitiallyTryWithNoAllocations(source, 0, predicate, selector, 0); + } + + private U[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func predicate, Func selector, int count) + { + T current; + (U, U, U, U) items; + + do + { + if (sourceIdx >= source.Count) + { + return Allocate(count); + } + + current = source[sourceIdx++]; + } while (!predicate(current)); + + items.Item1 = selector(current); + ++count; + + do + { + if (sourceIdx >= source.Count) + { + return AllocateAndAssign(count, items.Item1); + } + current = source[sourceIdx++]; + } + while (!predicate(current)); + + items.Item2 = selector(current); + ++count; + + do + { + if (sourceIdx >= source.Count) + { + return AllocateAndAssign(count, items.Item1, items.Item2); + } + current = source[sourceIdx++]; + } + while (!predicate(current)); + + items.Item3 = selector(current); + ++count; + + do + { + if (sourceIdx >= source.Count) + { + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); + } + current = source[sourceIdx++]; + } + while (!predicate(current)); + + items.Item4 = selector(current); + ++count; + + U[] result; + if (count >= maxSizeForNoAllocations) + { + if (sourceIdx < source.Count) + { + result = FinishViaAllocations(source, source.Count, ref sourceIdx, predicate, selector, count); + } + else + { + result = Allocate(count); + } + } + else + { + result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, selector, count); + } + + return Assign(result, ref items, count); + } + + protected override (int, bool) PopulateBuffer(U[] buffer, object source, ref int sourceIdx, object? predicate, object? selector) + { + Debug.Assert(predicate != null); + + List list = (List)source; + Func typedPredicate = (Func)predicate!; + Func typedSelector = (Func)selector!; + + var bufferIdx = PopulateBuffer(buffer, list, ref sourceIdx, typedPredicate, typedSelector); + + return (bufferIdx, sourceIdx < list.Count); + } + + private static int PopulateBuffer(U[] buffer, List source, ref int sourceIdx, Func predicate, Func selector) + { + int bufferIdx; + int arrayIdx; + + for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Count && bufferIdx < buffer.Length; ++arrayIdx) + { + var item = source[arrayIdx]; + + if (!predicate(item)) + continue; + + buffer[bufferIdx++] = selector(item); + } + + sourceIdx = arrayIdx; + return bufferIdx; + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj index 1649b1c7bd4dd0..869afd6422e280 100644 --- a/src/libraries/System.Linq/src/System.Linq.csproj +++ b/src/libraries/System.Linq/src/System.Linq.csproj @@ -32,6 +32,12 @@ System\Collections\Generic\LargeArrayBuilder.SpeedOpt.cs + + System\Collections\Generic\EnumerableHelpers.Linq.SpeedOpt.cs + + + System\Collections\Generic\ToArray.SpeedOpt.cs + diff --git a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs index 74a7ab667603c5..d8bb87a0ea4557 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs @@ -21,17 +21,7 @@ static partial void CreateSelectIPartitionIterator( private sealed partial class SelectEnumerableIterator : IIListProvider { - public TResult[] ToArray() - { - var builder = new LargeArrayBuilder(initialize: true); - - foreach (TSource item in _source) - { - builder.Add(_selector(item)); - } - - return builder.ToArray(); - } + public TResult[] ToArray() => ToArrayEnumerableWithSelect.Instance.ToArray(_source, _selector); public List ToList() { @@ -597,12 +587,7 @@ private TResult[] LazyToArray() { Debug.Assert(_source.GetCount(onlyIfCheap: true) == -1); - var builder = new LargeArrayBuilder(initialize: true); - foreach (TSource input in _source) - { - builder.Add(_selector(input)); - } - return builder.ToArray(); + return ToArrayEnumerableWithSelect.Instance.ToArray(_source, _selector); } private TResult[] PreallocatingToArray(int count) diff --git a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs index 83084e4353fc0f..28a37e65f4962a 100644 --- a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs @@ -33,20 +33,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TSource[] ToArray() - { - var builder = new LargeArrayBuilder(initialize: true); - - foreach (TSource item in _source) - { - if (_predicate(item)) - { - builder.Add(item); - } - } - - return builder.ToArray(); - } + public TSource[] ToArray() => ToArrayEnumerable.Instance.ToArray(_source, _predicate); public List ToList() { @@ -89,20 +76,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TSource[] ToArray() - { - var builder = new LargeArrayBuilder(_source.Length); - - foreach (TSource item in _source) - { - if (_predicate(item)) - { - builder.Add(item); - } - } - - return builder.ToArray(); - } + public TSource[] ToArray() => ToArrayArrayWhere.Instance.ToArray(_source, _predicate); public List ToList() { @@ -146,21 +120,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TSource[] ToArray() - { - var builder = new LargeArrayBuilder(_source.Count); - - for (int i = 0; i < _source.Count; i++) - { - TSource item = _source[i]; - if (_predicate(item)) - { - builder.Add(item); - } - } - - return builder.ToArray(); - } + public TSource[] ToArray() => ToArrayListWhere.Instance.ToArray(_source, _predicate); public List ToList() { @@ -208,20 +168,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TResult[] ToArray() - { - var builder = new LargeArrayBuilder(_source.Length); - - foreach (TSource item in _source) - { - if (_predicate(item)) - { - builder.Add(_selector(item)); - } - } - - return builder.ToArray(); - } + public TResult[] ToArray() => ToArrayArrayWhereSelect.Instance.ToArray(_source, _predicate, _selector); public List ToList() { @@ -269,21 +216,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TResult[] ToArray() - { - var builder = new LargeArrayBuilder(_source.Count); - - for (int i = 0; i < _source.Count; i++) - { - TSource item = _source[i]; - if (_predicate(item)) - { - builder.Add(_selector(item)); - } - } - - return builder.ToArray(); - } + public TResult[] ToArray() => ToArrayListWhereSelect.Instance.ToArray(_source, _predicate, _selector); public List ToList() { @@ -331,20 +264,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TResult[] ToArray() - { - var builder = new LargeArrayBuilder(initialize: true); - - foreach (TSource item in _source) - { - if (_predicate(item)) - { - builder.Add(_selector(item)); - } - } - - return builder.ToArray(); - } + public TResult[] ToArray() => ToArrayEnumerableWithSelect.Instance.ToArray(_source, _predicate, _selector); public List ToList() {