Skip to content

Commit

Permalink
Use CollectionsMarshal.SetCount in LINQ to deduplicate ToArray/ToList…
Browse files Browse the repository at this point in the history
… implementations (#85288)

* Use CollectionsMarshal.SetCount in LINQ to deduplicate ToArray/ToList implementations

* Fix BinaryFormatter test

The test was implemented using ToList, and `List<T>` serialization serializes out its version field.  As a result, because we're now optimizing creation and not incrementing the version as much in ToList, the blob didn't match.
  • Loading branch information
stephentoub authored Apr 25, 2023
1 parent 8e01ad8 commit b42626f
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 116 deletions.
11 changes: 11 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/Enumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ public static partial class Enumerable
{
public static IEnumerable<TSource> AsEnumerable<TSource>(this IEnumerable<TSource> source) => source;

/// <summary>
/// Sets the <paramref name="list"/>'s <see cref="List{T}.Count"/> to be <paramref name="count"/>
/// and returns the relevant portion of the list's backing array as a span.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Span<T> SetCountAndGetSpan<T>(List<T> list, int count)
{
CollectionsMarshal.SetCount(list, count);
return CollectionsMarshal.AsSpan(list);
}

/// <summary>Validates that source is not null and then tries to extract a span from the source.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // fast type checks that don't add a lot of overhead
private static bool TryGetSpan<TSource>(this IEnumerable<TSource> source, out ReadOnlySpan<TSource> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ public virtual TElement[] ToArray()
}

TElement[] array = new TElement[count];
int[] map = SortedMap(buffer);
for (int i = 0; i < array.Length; i++)
{
array[i] = buffer._items[map[i]];
}

Fill(buffer, array);
return array;
}

Expand All @@ -36,16 +31,21 @@ public virtual List<TElement> ToList()
List<TElement> list = new List<TElement>(count);
if (count > 0)
{
int[] map = SortedMap(buffer);
for (int i = 0; i != count; i++)
{
list.Add(buffer._items[map[i]]);
}
Fill(buffer, Enumerable.SetCountAndGetSpan(list, count));
}

return list;
}

private void Fill(Buffer<TElement> buffer, Span<TElement> destination)
{
int[] map = SortedMap(buffer);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = buffer._items[map[i]];
}
}

public int GetCount(bool onlyIfCheap)
{
if (_source is IIListProvider<TElement> listProv)
Expand Down Expand Up @@ -75,15 +75,9 @@ internal TElement[] ToArray(int minIdx, int maxIdx)
return new TElement[] { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) };
}

int[] map = SortedMap(buffer, minIdx, maxIdx);
TElement[] array = new TElement[maxIdx - minIdx + 1];
int idx = 0;
while (minIdx <= maxIdx)
{
array[idx] = buffer._items[map[minIdx]];
++idx;
++minIdx;
}

Fill(minIdx, maxIdx, buffer, array);

return array;
}
Expand All @@ -107,15 +101,21 @@ internal List<TElement> ToList(int minIdx, int maxIdx)
return new List<TElement>(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) };
}

int[] map = SortedMap(buffer, minIdx, maxIdx);
List<TElement> list = new List<TElement>(maxIdx - minIdx + 1);
Fill(minIdx, maxIdx, buffer, Enumerable.SetCountAndGetSpan(list, maxIdx - minIdx + 1));
return list;
}

private void Fill(int minIdx, int maxIdx, Buffer<TElement> buffer, Span<TElement> destination)
{
int[] map = SortedMap(buffer, minIdx, maxIdx);
int idx = 0;
while (minIdx <= maxIdx)
{
list.Add(buffer._items[map[minIdx]]);
destination[idx] = buffer._items[map[minIdx]];
++idx;
++minIdx;
}

return list;
}

internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap)
Expand Down
19 changes: 9 additions & 10 deletions src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,7 @@ public TSource[] ToArray()
}

TSource[] array = new TSource[count];
for (int i = 0, curIdx = _minIndexInclusive; i < array.Length; ++i, ++curIdx)
{
array[i] = _source[curIdx];
}

Fill(_source, array, _minIndexInclusive);
return array;
}

Expand All @@ -271,13 +267,16 @@ public List<TSource> ToList()
}

List<TSource> list = new List<TSource>(count);
int end = _minIndexInclusive + count;
for (int i = _minIndexInclusive; i != end; ++i)
Fill(_source, SetCountAndGetSpan(list, count), _minIndexInclusive);
return list;
}

private static void Fill(IList<TSource> source, Span<TSource> destination, int sourceIndex)
{
for (int i = 0; i < destination.Length; i++, sourceIndex++)
{
list.Add(_source[i]);
destination[i] = source[sourceIndex];
}

return list;
}

public int GetCount(bool onlyIfCheap) => Count;
Expand Down
20 changes: 9 additions & 11 deletions src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,23 @@ public override IEnumerable<TResult> Select<TResult>(Func<int, TResult> selector
public int[] ToArray()
{
int[] array = new int[_end - _start];
int cur = _start;
for (int i = 0; i < array.Length; ++i)
{
array[i] = cur;
++cur;
}

Fill(array, _start);
return array;
}

public List<int> ToList()
{
List<int> list = new List<int>(_end - _start);
for (int cur = _start; cur != _end; cur++)
Fill(SetCountAndGetSpan(list, _end - _start), _start);
return list;
}

private static void Fill(Span<int> destination, int value)
{
for (int i = 0; i < destination.Length; i++, value++)
{
list.Add(cur);
destination[i] = value;
}

return list;
}

public int GetCount(bool onlyIfCheap) => unchecked(_end - _start);
Expand Down
5 changes: 1 addition & 4 deletions src/libraries/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ public TResult[] ToArray()
public List<TResult> ToList()
{
List<TResult> list = new List<TResult>(_count);
for (int i = 0; i != _count; ++i)
{
list.Add(_current);
}
SetCountAndGetSpan(list, _count).Fill(_current);

return list;
}
Expand Down
Loading

0 comments on commit b42626f

Please sign in to comment.