From b423a00cfcb182af204b4f5baf49780b27cebb95 Mon Sep 17 00:00:00 2001 From: Paul Westcott Date: Tue, 3 Dec 2019 17:50:17 +1100 Subject: [PATCH 1/9] Alternative ToArray implementation --- .../Generic/EnumerableHelpers.Linq.SizeOpt.cs | 40 ++ .../EnumerableHelpers.Linq.SpeedOpt.cs | 20 + .../Generic/EnumerableHelpers.Linq.cs | 25 -- .../Collections/Generic/ToArray.SpeedOpt.cs | 354 ++++++++++++++++++ .../System.Linq/src/System.Linq.csproj | 6 + .../src/System/Linq/Where.SpeedOpt.cs | 30 +- 6 files changed, 422 insertions(+), 53 deletions(-) create mode 100644 src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SizeOpt.cs create mode 100644 src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SpeedOpt.cs create mode 100644 src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs 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..606fd7c0271aea --- /dev/null +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -0,0 +1,354 @@ +// 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 + { + private const int maxArraySize = int.MaxValue; + + private const int initialRecursiveDepth = 16; + private const int itemsPerRecursion = 4; + + protected const int maxSizeForNoAllocations = initialRecursiveDepth * itemsPerRecursion; + + protected T[] FinishViaAllocations(object source, ref int sourceIdx, Func? predicate, int count) + { + if (count == maxArraySize) + { + throw new IndexOutOfRangeException(); + } + + T[] result; + int bufferSize = (int)Math.Min((uint)maxArraySize, (uint)count * 2) - count; + T[] buffer = new T[bufferSize]; + + var (index, moveNext) = PopulateBuffer(buffer, source, ref sourceIdx, predicate); + + if (moveNext) + { + result = FinishViaAllocations(source, ref sourceIdx, predicate, 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, Func? predicate); + + 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; + } + } + + 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, null, 0); + } + + public T[] ToArray(IEnumerable source, Func predicate) + { + Debug.Assert(source != null); + using IEnumerator e = source.GetEnumerator(); + return InitiallyTryWithNoAllocations(e, predicate, 0); + } + + protected T[] InitiallyTryWithNoAllocations(IEnumerator e, Func? predicate, int count) + { + T[] result; + T item1, item2, item3, item4; + + do + { + if (!e.MoveNext()) + { + return Allocate(count); + } + item1 = e.Current; + } + while (predicate != null && !predicate(item1)); + + ++count; + + do + { + if (!e.MoveNext()) + { + return AllocateAndAssign(count, item1); + } + item2 = e.Current; + } while (predicate != null && !predicate(item2)); + + ++count; + + do + { + if (!e.MoveNext()) + { + return AllocateAndAssign(count, item1, item2); + } + item3 = e.Current; + } + while (predicate != null && !predicate(item3)); + + ++count; + + do + { + if (!e.MoveNext()) + { + return AllocateAndAssign(count, item1, item2, item3); + } + item4 = e.Current; + } + while (predicate != null && !predicate(item4)); + + ++count; + + if (count >= maxSizeForNoAllocations) + { + if (e.MoveNext()) + result = FinishViaAllocations(e, ref count, predicate, count); + else + result = Allocate(count); + } + else + { + result = InitiallyTryWithNoAllocations(e, predicate, count); + } + + result[--count] = item4; + result[--count] = item3; + result[--count] = item2; + result[--count] = item1; + + return result; + } + + protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate) + { + var enumerator = (IEnumerator)source; + return predicate == null ? PopulateBuffer(buffer, enumerator) : PopulateBuffer(buffer, enumerator, predicate); + } + + private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e) + { + bool moveNext; + int index; + + for (moveNext = true, index = 0; moveNext && index < buffer.Length; ++index) + { + buffer[index] = e.Current; + moveNext = e.MoveNext(); + } + + return (index, moveNext); + } + + private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e, Func predicate) + { + bool moveNext; + int index; + + for (moveNext = true, index = 0; moveNext;) + { + var item = e.Current; + if (predicate(item)) + { + buffer[index++] = e.Current; + if (index >= buffer.Length) + { + moveNext = e.MoveNext(); + break; + } + } + moveNext = e.MoveNext(); + } + + return (index, moveNext); + } + } + + internal class ToArrayArray : ToArrayBase + { + public static readonly ToArrayArray Instance = new ToArrayArray(); + + private ToArrayArray() { } + + public T[] ToArray(T[] source, Func predicate) + { + Debug.Assert(source != null); + + return InitiallyTryWithNoAllocations(source, 0, predicate, 0); + } + + protected T[] InitiallyTryWithNoAllocations(T[] array, int arrayIdx, Func predicate, int count) + { + T[] result; + T item1, item2, item3, item4; + + do + { + if (arrayIdx >= array.Length) + { + return Allocate(count); + } + + item1 = array[arrayIdx++]; + } while (!predicate(item1)); + + ++count; + + do + { + if (arrayIdx >= array.Length) + { + return AllocateAndAssign(count, item1); + } + item2 = array[arrayIdx++]; + } + while (!predicate(item2)); + + ++count; + + do + { + if (arrayIdx >= array.Length) + { + return AllocateAndAssign(count, item1, item2); + } + item3 = array[arrayIdx++]; + } + while (!predicate(item3)); + + ++count; + + do + { + if (arrayIdx >= array.Length) + { + return AllocateAndAssign(count, item1, item2, item3); + } + item4 = array[arrayIdx++]; + } + while (!predicate(item4)); + + ++count; + + if (count >= maxSizeForNoAllocations) + { + if (arrayIdx < array.Length) + { + result = FinishViaAllocations(array, ref arrayIdx, predicate, count); + } + else + { + result = Allocate(count); + } + } + else + { + result = InitiallyTryWithNoAllocations(array, arrayIdx, predicate, count); + } + + result[--count] = item4; + result[--count] = item3; + result[--count] = item2; + result[--count] = item1; + + return result; + } + + protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate) + { + Debug.Assert(predicate != null); + + T[] array = (T[])source; + + var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, predicate); + + return (bufferIdx, sourceIdx < array.Length); + } + + private int PopulateBuffer(T[] buffer, T[] source, ref int sourceIdx, Func predicate) + { + int bufferIdx; + int arrayIdx; + + for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Length; ++arrayIdx) + { + if (predicate(source[arrayIdx])) + { + buffer[bufferIdx++] = source[arrayIdx]; + + if (bufferIdx >= buffer.Length) + { + ++arrayIdx; + break; + } + } + } + + 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/Where.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs index 83084e4353fc0f..629c8a09eb67ba 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() => ToArrayArray.Instance.ToArray(_source, _predicate); public List ToList() { From f9735f20e693521d0a89124d0f49369392deb066 Mon Sep 17 00:00:00 2001 From: Paul Westcott Date: Wed, 4 Dec 2019 18:45:30 +1100 Subject: [PATCH 2/9] Added ToList for List --- .../Collections/Generic/ToArray.SpeedOpt.cs | 133 ++++++++++++++++-- .../src/System/Linq/Where.SpeedOpt.cs | 16 +-- 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index 606fd7c0271aea..de8fe3f87ebd5f 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -333,18 +333,135 @@ private int PopulateBuffer(T[] buffer, T[] source, ref int sourceIdx, Func : ToArrayBase + { + public static readonly ToArrayList Instance = new ToArrayList(); + + private ToArrayList() { } + + public T[] ToArray(List source, Func predicate) + { + Debug.Assert(source != null); + + return InitiallyTryWithNoAllocations(source, 0, predicate, 0); + } + + protected T[] InitiallyTryWithNoAllocations(List array, int arrayIdx, Func predicate, int count) + { + T[] result; + T item1, item2, item3, item4; + + do + { + if (arrayIdx >= array.Count) { - buffer[bufferIdx++] = source[arrayIdx]; + return Allocate(count); + } - if (bufferIdx >= buffer.Length) - { - ++arrayIdx; - break; - } + item1 = array[arrayIdx++]; + } while (!predicate(item1)); + + ++count; + + do + { + if (arrayIdx >= array.Count) + { + return AllocateAndAssign(count, item1); + } + item2 = array[arrayIdx++]; + } + while (!predicate(item2)); + + ++count; + + do + { + if (arrayIdx >= array.Count) + { + return AllocateAndAssign(count, item1, item2); } + item3 = array[arrayIdx++]; + } + while (!predicate(item3)); + + ++count; + + do + { + if (arrayIdx >= array.Count) + { + return AllocateAndAssign(count, item1, item2, item3); + } + item4 = array[arrayIdx++]; + } + while (!predicate(item4)); + + ++count; + + if (count >= maxSizeForNoAllocations) + { + if (arrayIdx < array.Count) + { + result = FinishViaAllocations(array, ref arrayIdx, predicate, count); + } + else + { + result = Allocate(count); + } + } + else + { + result = InitiallyTryWithNoAllocations(array, arrayIdx, predicate, count); + } + + result[--count] = item4; + result[--count] = item3; + result[--count] = item2; + result[--count] = item1; + + return result; + } + + protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate) + { + Debug.Assert(predicate != null); + + List array = (List)source; + + var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, predicate); + + return (bufferIdx, sourceIdx < array.Count); + } + + private 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; 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 629c8a09eb67ba..705bc9c31825aa 100644 --- a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs @@ -120,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() => ToArrayList.Instance.ToArray(_source, _predicate); public List ToList() { From 33388d3a94993bad34c7e1e19d55bfe03f7e497d Mon Sep 17 00:00:00 2001 From: Paul Westcott Date: Wed, 4 Dec 2019 20:15:34 +1100 Subject: [PATCH 3/9] Tried different inner loop for generator with predicate --- .../Collections/Generic/ToArray.SpeedOpt.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index de8fe3f87ebd5f..f709a55641e8ac 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -208,19 +208,14 @@ private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e, Func= buffer.Length) - { - moveNext = e.MoveNext(); - break; - } - } - moveNext = e.MoveNext(); + + if (!predicate(item)) + continue; + + buffer[index++] = e.Current; } return (index, moveNext); From 8282a661459349723be86dbedda5cf21ad0d5e2b Mon Sep 17 00:00:00 2001 From: Paul Westcott Date: Thu, 5 Dec 2019 16:33:05 +1100 Subject: [PATCH 4/9] Consolidated Assign functionality, and made code more consistent --- .../Collections/Generic/ToArray.SpeedOpt.cs | 175 ++++++++++-------- 1 file changed, 96 insertions(+), 79 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index f709a55641e8ac..511ab22141b7bf 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -39,6 +39,7 @@ protected T[] FinishViaAllocations(object source, ref int sourceIdx, Func source, Func predicate) return InitiallyTryWithNoAllocations(e, predicate, 0); } - protected T[] InitiallyTryWithNoAllocations(IEnumerator e, Func? predicate, int count) + protected T[] InitiallyTryWithNoAllocations(IEnumerator source, Func? predicate, int count) { - T[] result; - T item1, item2, item3, item4; + (T, T, T, T) items; do { - if (!e.MoveNext()) + if (!source.MoveNext()) { return Allocate(count); } - item1 = e.Current; + items.Item1 = source.Current; } - while (predicate != null && !predicate(item1)); + while (predicate != null && !predicate(items.Item1)); ++count; do { - if (!e.MoveNext()) + if (!source.MoveNext()) { - return AllocateAndAssign(count, item1); + return AllocateAndAssign(count, items.Item1); } - item2 = e.Current; - } while (predicate != null && !predicate(item2)); + items.Item2 = source.Current; + } while (predicate != null && !predicate(items.Item2)); ++count; do { - if (!e.MoveNext()) + if (!source.MoveNext()) { - return AllocateAndAssign(count, item1, item2); + return AllocateAndAssign(count, items.Item1, items.Item2); } - item3 = e.Current; + items.Item3 = source.Current; } - while (predicate != null && !predicate(item3)); + while (predicate != null && !predicate(items.Item3)); ++count; do { - if (!e.MoveNext()) + if (!source.MoveNext()) { - return AllocateAndAssign(count, item1, item2, item3); + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); } - item4 = e.Current; + items.Item4 = source.Current; } - while (predicate != null && !predicate(item4)); + while (predicate != null && !predicate(items.Item4)); ++count; + T[] result; if (count >= maxSizeForNoAllocations) { - if (e.MoveNext()) - result = FinishViaAllocations(e, ref count, predicate, count); + if (source.MoveNext()) + result = FinishViaAllocations(source, predicate, count); else result = Allocate(count); } else { - result = InitiallyTryWithNoAllocations(e, predicate, count); + result = InitiallyTryWithNoAllocations(source, predicate, count); } - result[--count] = item4; - result[--count] = item3; - result[--count] = item2; - result[--count] = item1; + return Assign(result, ref items, count); + } - return result; + private T[] FinishViaAllocations(IEnumerator source, Func? predicate, int count) + { + int dummyIdx = 0; + return FinishViaAllocations(source, ref dummyIdx, predicate, count); } protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate) @@ -194,10 +212,9 @@ private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e) bool moveNext; int index; - for (moveNext = true, index = 0; moveNext && index < buffer.Length; ++index) + for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext(), ++index) { buffer[index] = e.Current; - moveNext = e.MoveNext(); } return (index, moveNext); @@ -215,7 +232,7 @@ private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e, Func predicate) return InitiallyTryWithNoAllocations(source, 0, predicate, 0); } - protected T[] InitiallyTryWithNoAllocations(T[] array, int arrayIdx, Func predicate, int count) + protected T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func predicate, int count) { - T[] result; - T item1, item2, item3, item4; + (T, T, T, T) items; do { - if (arrayIdx >= array.Length) + if (sourceIdx >= source.Length) { return Allocate(count); } - item1 = array[arrayIdx++]; - } while (!predicate(item1)); + items.Item1 = source[sourceIdx++]; + } while (!predicate(items.Item1)); ++count; do { - if (arrayIdx >= array.Length) + if (sourceIdx >= source.Length) { - return AllocateAndAssign(count, item1); + return AllocateAndAssign(count, items.Item1); } - item2 = array[arrayIdx++]; + items.Item2 = source[sourceIdx++]; } - while (!predicate(item2)); + while (!predicate(items.Item2)); ++count; do { - if (arrayIdx >= array.Length) + if (sourceIdx >= source.Length) { - return AllocateAndAssign(count, item1, item2); + return AllocateAndAssign(count, items.Item1, items.Item2); } - item3 = array[arrayIdx++]; + items.Item3 = source[sourceIdx++]; } - while (!predicate(item3)); + while (!predicate(items.Item3)); ++count; do { - if (arrayIdx >= array.Length) + if (sourceIdx >= source.Length) { - return AllocateAndAssign(count, item1, item2, item3); + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); } - item4 = array[arrayIdx++]; + items.Item4 = source[sourceIdx++]; } - while (!predicate(item4)); + while (!predicate(items.Item4)); ++count; + T[] result; if (count >= maxSizeForNoAllocations) { - if (arrayIdx < array.Length) + if (sourceIdx < source.Length) { - result = FinishViaAllocations(array, ref arrayIdx, predicate, count); + result = FinishViaAllocations(source, ref sourceIdx, predicate, count); } else { @@ -301,13 +318,13 @@ protected T[] InitiallyTryWithNoAllocations(T[] array, int arrayIdx, Func source, Func predicate) return InitiallyTryWithNoAllocations(source, 0, predicate, 0); } - protected T[] InitiallyTryWithNoAllocations(List array, int arrayIdx, Func predicate, int count) + protected T[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func predicate, int count) { - T[] result; - T item1, item2, item3, item4; + (T, T, T, T) items; do { - if (arrayIdx >= array.Count) + if (sourceIdx >= source.Count) { return Allocate(count); } - item1 = array[arrayIdx++]; - } while (!predicate(item1)); + items.Item1 = source[sourceIdx++]; + } while (!predicate(items.Item1)); ++count; do { - if (arrayIdx >= array.Count) + if (sourceIdx >= source.Count) { - return AllocateAndAssign(count, item1); + return AllocateAndAssign(count, items.Item1); } - item2 = array[arrayIdx++]; + items.Item2 = source[sourceIdx++]; } - while (!predicate(item2)); + while (!predicate(items.Item2)); ++count; do { - if (arrayIdx >= array.Count) + if (sourceIdx >= source.Count) { - return AllocateAndAssign(count, item1, item2); + return AllocateAndAssign(count, items.Item1, items.Item2); } - item3 = array[arrayIdx++]; + items.Item3 = source[sourceIdx++]; } - while (!predicate(item3)); + while (!predicate(items.Item3)); ++count; do { - if (arrayIdx >= array.Count) + if (sourceIdx >= source.Count) { - return AllocateAndAssign(count, item1, item2, item3); + return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3); } - item4 = array[arrayIdx++]; + items.Item4 = source[sourceIdx++]; } - while (!predicate(item4)); + while (!predicate(items.Item4)); ++count; + T[] result; if (count >= maxSizeForNoAllocations) { - if (arrayIdx < array.Count) + if (sourceIdx < source.Count) { - result = FinishViaAllocations(array, ref arrayIdx, predicate, count); + result = FinishViaAllocations(source, ref sourceIdx, predicate, count); } else { @@ -422,13 +439,13 @@ protected T[] InitiallyTryWithNoAllocations(List array, int arrayIdx, Func Date: Sat, 7 Dec 2019 07:56:33 +1100 Subject: [PATCH 5/9] Consolidation --- .../Collections/Generic/ToArray.SpeedOpt.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index 511ab22141b7bf..9474cc0a84ecd5 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -321,12 +321,7 @@ protected T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func? predicate) @@ -340,7 +335,7 @@ protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int return (bufferIdx, sourceIdx < array.Length); } - private int PopulateBuffer(T[] buffer, T[] source, ref int sourceIdx, Func predicate) + private static int PopulateBuffer(T[] buffer, T[] source, ref int sourceIdx, Func predicate) { int bufferIdx; int arrayIdx; @@ -442,12 +437,7 @@ protected T[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func< result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, count); } - result[--count] = items.Item4; - result[--count] = items.Item3; - result[--count] = items.Item2; - result[--count] = items.Item1; - - return result; + return Assign(result, ref items, count); } protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate) @@ -461,7 +451,7 @@ protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int return (bufferIdx, sourceIdx < array.Count); } - private int PopulateBuffer(T[] buffer, List source, ref int sourceIdx, Func predicate) + private static int PopulateBuffer(T[] buffer, List source, ref int sourceIdx, Func predicate) { int bufferIdx; int arrayIdx; From c27bf9db9048a04d70003ce45aee1f29a8a9370b Mon Sep 17 00:00:00 2001 From: Paul Westcott Date: Sat, 7 Dec 2019 08:00:48 +1100 Subject: [PATCH 6/9] Added predicate-less enumerable version --- .../Collections/Generic/ToArray.SpeedOpt.cs | 84 ++++++++++++++++--- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index 9474cc0a84ecd5..ec3b6252c39e54 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -118,7 +118,7 @@ public T[] ToArray(IEnumerable source) } using IEnumerator e = source.GetEnumerator(); - return InitiallyTryWithNoAllocations(e, null, 0); + return InitiallyTryWithNoAllocations(e, 0); } public T[] ToArray(IEnumerable source, Func predicate) @@ -128,7 +128,65 @@ public T[] ToArray(IEnumerable source, Func predicate) return InitiallyTryWithNoAllocations(e, predicate, 0); } - protected T[] InitiallyTryWithNoAllocations(IEnumerator source, Func? predicate, int count) + 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, ref dummyIdx, null, count); + } + } + + private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func predicate, int count) { (T, T, T, T) items; @@ -140,7 +198,7 @@ protected T[] InitiallyTryWithNoAllocations(IEnumerator source, Func } items.Item1 = source.Current; } - while (predicate != null && !predicate(items.Item1)); + while (predicate(items.Item1)); ++count; @@ -151,7 +209,7 @@ protected T[] InitiallyTryWithNoAllocations(IEnumerator source, Func return AllocateAndAssign(count, items.Item1); } items.Item2 = source.Current; - } while (predicate != null && !predicate(items.Item2)); + } while (predicate(items.Item2)); ++count; @@ -163,7 +221,7 @@ protected T[] InitiallyTryWithNoAllocations(IEnumerator source, Func } items.Item3 = source.Current; } - while (predicate != null && !predicate(items.Item3)); + while (predicate(items.Item3)); ++count; @@ -175,7 +233,7 @@ protected T[] InitiallyTryWithNoAllocations(IEnumerator source, Func } items.Item4 = source.Current; } - while (predicate != null && !predicate(items.Item4)); + while (predicate(items.Item4)); ++count; @@ -193,12 +251,12 @@ protected T[] InitiallyTryWithNoAllocations(IEnumerator source, Func } return Assign(result, ref items, count); - } - private T[] FinishViaAllocations(IEnumerator source, Func? predicate, int count) - { - int dummyIdx = 0; - return FinishViaAllocations(source, ref dummyIdx, predicate, count); + T[] FinishViaAllocations(IEnumerator source, Func? predicate, int count) + { + int dummyIdx = 0; + return base.FinishViaAllocations(source, ref dummyIdx, predicate, count); + } } protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate) @@ -252,7 +310,7 @@ public T[] ToArray(T[] source, Func predicate) return InitiallyTryWithNoAllocations(source, 0, predicate, 0); } - protected T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func predicate, int count) + private T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func predicate, int count) { (T, T, T, T) items; @@ -368,7 +426,7 @@ public T[] ToArray(List source, Func predicate) return InitiallyTryWithNoAllocations(source, 0, predicate, 0); } - protected T[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func predicate, int count) + private T[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func predicate, int count) { (T, T, T, T) items; From eb7f136f8a2cdee9e10fccdc456b36ec8ce49cdd Mon Sep 17 00:00:00 2001 From: Paul Westcott Date: Sat, 7 Dec 2019 08:29:51 +1100 Subject: [PATCH 7/9] Added sizing to minimize allocations and handle maximum size --- .../Collections/Generic/ToArray.SpeedOpt.cs | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index ec3b6252c39e54..664a0460b05a20 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -9,29 +9,47 @@ namespace System.Collections.Generic { internal abstract class ToArrayBase { - private const int maxArraySize = int.MaxValue; + // 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; - protected T[] FinishViaAllocations(object source, ref int sourceIdx, Func? predicate, int count) + private int GetBufferSize(int count, int sourceSize) { - if (count == maxArraySize) + if (count >= maxByteElementsArraySize) { - throw new IndexOutOfRangeException(); + // 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, Func? predicate, int count) + { T[] result; - int bufferSize = (int)Math.Min((uint)maxArraySize, (uint)count * 2) - count; + int bufferSize = GetBufferSize(count, sourceSize); T[] buffer = new T[bufferSize]; var (index, moveNext) = PopulateBuffer(buffer, source, ref sourceIdx, predicate); if (moveNext) { - result = FinishViaAllocations(source, ref sourceIdx, predicate, count + index); + result = FinishViaAllocations(source, sourceSize, ref sourceIdx, predicate, count + index); } else { @@ -182,7 +200,7 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, int count) T[] FinishViaAllocations(IEnumerator source, int count) { int dummyIdx = 0; - return base.FinishViaAllocations(source, ref dummyIdx, null, count); + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, null, count); } } @@ -198,7 +216,7 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func p } items.Item1 = source.Current; } - while (predicate(items.Item1)); + while (!predicate(items.Item1)); ++count; @@ -209,7 +227,7 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func p return AllocateAndAssign(count, items.Item1); } items.Item2 = source.Current; - } while (predicate(items.Item2)); + } while (!predicate(items.Item2)); ++count; @@ -221,7 +239,7 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func p } items.Item3 = source.Current; } - while (predicate(items.Item3)); + while (!predicate(items.Item3)); ++count; @@ -233,7 +251,7 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func p } items.Item4 = source.Current; } - while (predicate(items.Item4)); + while (!predicate(items.Item4)); ++count; @@ -255,7 +273,7 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func p T[] FinishViaAllocations(IEnumerator source, Func? predicate, int count) { int dummyIdx = 0; - return base.FinishViaAllocations(source, ref dummyIdx, predicate, count); + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, predicate, count); } } @@ -367,7 +385,7 @@ private T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func source, int sourceIdx, Func Date: Sat, 7 Dec 2019 11:43:41 +1100 Subject: [PATCH 8/9] Removed duplicate semicolon Co-Authored-By: Leandro A. F. Pereira --- .../Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index 664a0460b05a20..428862389de096 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -36,7 +36,7 @@ private int GetBufferSize(int count, int sourceSize) ? maxOtherElementsArraySize : maxByteElementsArraySize; - return (int)Math.Min((uint)maxSize, (uint)count * 2) - count; ; + return (int)Math.Min((uint)maxSize, (uint)count * 2) - count; } protected T[] FinishViaAllocations(object source, int sourceSize, ref int sourceIdx, Func? predicate, int count) @@ -546,4 +546,4 @@ private static int PopulateBuffer(T[] buffer, List source, ref int sourceIdx, return bufferIdx; } } -} \ No newline at end of file +} From 1b1c03a19f30d1f5e8308fb85f6ff5b7b1815886 Mon Sep 17 00:00:00 2001 From: Paul Westcott Date: Sat, 7 Dec 2019 14:19:35 +1100 Subject: [PATCH 9/9] Added Select based ones... --- .../Collections/Generic/ToArray.SpeedOpt.cs | 512 +++++++++++++++++- .../src/System/Linq/Select.SpeedOpt.cs | 19 +- .../src/System/Linq/Where.SpeedOpt.cs | 50 +- 3 files changed, 499 insertions(+), 82 deletions(-) diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs index 664a0460b05a20..39494d8e1439ff 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs @@ -39,17 +39,17 @@ private int GetBufferSize(int count, int sourceSize) return (int)Math.Min((uint)maxSize, (uint)count * 2) - count; ; } - protected T[] FinishViaAllocations(object source, int sourceSize, ref int sourceIdx, Func? predicate, int 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); + var (index, moveNext) = PopulateBuffer(buffer, source, ref sourceIdx, predicate, selector); if (moveNext) { - result = FinishViaAllocations(source, sourceSize, ref sourceIdx, predicate, count + index); + result = FinishViaAllocations(source, sourceSize, ref sourceIdx, predicate, selector, count + index); } else { @@ -61,7 +61,7 @@ protected T[] FinishViaAllocations(object source, int sourceSize, ref int source return result; } - protected abstract (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate); + protected abstract (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector); protected static T[] Allocate(int count) { @@ -200,7 +200,7 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, int count) T[] FinishViaAllocations(IEnumerator source, int count) { int dummyIdx = 0; - return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, null, count); + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, null, null, count); } } @@ -273,14 +273,17 @@ private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func p T[] FinishViaAllocations(IEnumerator source, Func? predicate, int count) { int dummyIdx = 0; - return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, predicate, count); + return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, predicate, null, count); } } - protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, Func? predicate) + protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector) { var enumerator = (IEnumerator)source; - return predicate == null ? PopulateBuffer(buffer, enumerator) : PopulateBuffer(buffer, enumerator, predicate); + + Func typedPredicate = (Func)predicate!; + + return predicate == null ? PopulateBuffer(buffer, enumerator) : PopulateBuffer(buffer, enumerator, typedPredicate); } private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e) @@ -315,11 +318,232 @@ private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e, Func : ToArrayBase + 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 ToArrayArray Instance = new ToArrayArray(); + public static readonly ToArrayArrayWhere Instance = new ToArrayArrayWhere(); - private ToArrayArray() { } + private ToArrayArrayWhere() { } public T[] ToArray(T[] source, Func predicate) { @@ -385,7 +609,7 @@ private T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func? predicate) + 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, predicate); + var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate); return (bufferIdx, sourceIdx < array.Length); } @@ -431,11 +656,134 @@ private static int PopulateBuffer(T[] buffer, T[] source, ref int sourceIdx, Fun } } - internal class ToArrayList : ToArrayBase + internal class ToArrayArrayWhereSelect : ToArrayBase { - public static readonly ToArrayList Instance = new ToArrayList(); + public static readonly ToArrayArrayWhereSelect Instance = new ToArrayArrayWhereSelect(); + + private ToArrayArrayWhereSelect() { } - private ToArrayList() { } + 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) { @@ -501,7 +849,7 @@ private T[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func source, int sourceIdx, Func? predicate) + 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, predicate); + var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate); return (bufferIdx, sourceIdx < array.Count); } @@ -546,4 +895,127 @@ private static int PopulateBuffer(T[] buffer, List source, ref int sourceIdx, 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/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 705bc9c31825aa..28a37e65f4962a 100644 --- a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs @@ -76,7 +76,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TSource[] ToArray() => ToArrayArray.Instance.ToArray(_source, _predicate); + public TSource[] ToArray() => ToArrayArrayWhere.Instance.ToArray(_source, _predicate); public List ToList() { @@ -120,7 +120,7 @@ public int GetCount(bool onlyIfCheap) return count; } - public TSource[] ToArray() => ToArrayList.Instance.ToArray(_source, _predicate); + public TSource[] ToArray() => ToArrayListWhere.Instance.ToArray(_source, _predicate); public List ToList() { @@ -168,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() { @@ -229,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() { @@ -291,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() {