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