Skip to content

Introduce size-optimized IListSelect iterator #118156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/libraries/System.Linq/src/System.Linq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<Compile Include="System\Linq\RightJoin.cs" />
<Compile Include="System\Linq\SegmentedArrayBuilder.cs" />
<Compile Include="System\Linq\Select.cs" />
<Compile Include="System\Linq\Select.SizeOpt.cs" />
<Compile Include="System\Linq\Select.SpeedOpt.cs" />
<Compile Include="System\Linq\SelectMany.cs" />
<Compile Include="System\Linq\SelectMany.SpeedOpt.cs" />
Expand All @@ -70,19 +71,18 @@
<Compile Include="System\Linq\Single.cs" />
<Compile Include="System\Linq\SingleLinkedNode.cs" />
<Compile Include="System\Linq\Skip.cs" />
<Compile Include="System\Linq\Skip.SizeOpt.cs" />
<Compile Include="System\Linq\Skip.SpeedOpt.cs" />
<Compile Include="System\Linq\SkipTake.SpeedOpt.cs" />
<Compile Include="System\Linq\Sum.cs" />
<Compile Include="System\Linq\Take.cs" />
<Compile Include="System\Linq\Take.SizeOpt.cs" />
<Compile Include="System\Linq\Take.SpeedOpt.cs" />
<Compile Include="System\Linq\ThrowHelper.cs" />
<Compile Include="System\Linq\ToCollection.cs" />
<Compile Include="System\Linq\Union.cs" />
<Compile Include="System\Linq\Union.SpeedOpt.cs" />
<Compile Include="System\Linq\Utilities.cs" />
<Compile Include="System\Linq\Where.cs" />
<Compile Include="System\Linq\Where.SizeOpt.cs" />
<Compile Include="System\Linq\Where.SpeedOpt.cs" />
<Compile Include="System\Linq\Zip.cs" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Linq/src/System/Linq/Count.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static int Count<TSource>(this IEnumerable<TSource> source)
return collectionoft.Count;
}

if (!IsSizeOptimized && source is Iterator<TSource> iterator)
if (source is Iterator<TSource> iterator)
{
return iterator.GetCount(onlyIfCheap: false);
}
Expand Down Expand Up @@ -113,7 +113,7 @@ public static bool TryGetNonEnumeratedCount<TSource>(this IEnumerable<TSource> s
return true;
}

if (!IsSizeOptimized && source is Iterator<TSource> iterator)
if (source is Iterator<TSource> iterator)
{
int c = iterator.GetCount(onlyIfCheap: true);
if (c >= 0)
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Linq/src/System/Linq/ElementAt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int i

bool found;
TSource? element =
!IsSizeOptimized && source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
TryGetElementAtNonIterator(source, index, out found);

if (!found)
Expand Down Expand Up @@ -121,7 +121,7 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, Index
}

return
!IsSizeOptimized && source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
TryGetElementAtNonIterator(source, index, out found);
}

Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Linq/src/System/Linq/Last.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, F
}

return
!IsSizeOptimized && source is Iterator<TSource> iterator ? iterator.TryGetLast(out found) :
source is Iterator<TSource> iterator ? iterator.TryGetLast(out found) :
TryGetLastNonIterator(source, out found);
}

Expand Down
8 changes: 6 additions & 2 deletions src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public override IEnumerable<TResult2> Select<TResult2>(Func<TResult, TResult2> s
// they're covariant. It's not worthwhile checking for List<T> to use the ListWhereSelectIterator
// because List<> is not covariant.
Func<object, bool> isTResult = static o => o is TResult;
return objectSource is object[] array ?
return !IsSizeOptimized && objectSource is object[] array ?
new ArrayWhereSelectIterator<object, TResult2>(array, isTResult, localSelector) :
new IEnumerableWhereSelectIterator<object, TResult2>(objectSource, isTResult, localSelector);
}
Expand All @@ -177,7 +177,11 @@ public override IEnumerable<TResult2> Select<TResult2>(Func<TResult, TResult2> s

public override bool Contains(TResult value)
{
if (!typeof(TResult).IsValueType && // don't box TResult
// Avoid checking for IList when size-optimized because it keeps IList
// implementations which may otherwise be trimmed. Since List<T> implements
// IList and List<T> is popular, this could potentially be a lot of code.
if (!IsSizeOptimized &&
!typeof(TResult).IsValueType && // don't box TResult
_source is IList list)
{
return list.Contains(value);
Expand Down
90 changes: 90 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/Select.SizeOpt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace System.Linq
{
public static partial class Enumerable
{
private sealed class SizeOptIListSelectIterator<TSource, TResult>(IList<TSource> _source, Func<TSource, TResult> _selector)
: Iterator<TResult>
{
public override int GetCount(bool onlyIfCheap)
{
// In case someone uses Count() to force evaluation of
// the selector, run it provided `onlyIfCheap` is false.

if (onlyIfCheap)
{
return -1;
}

int count = 0;

foreach (TSource item in _source)
{
_selector(item);
checked
{
count++;
}
}

return count;
}

public override Iterator<TResult> Skip(int count)
{
Debug.Assert(count > 0);
return new IListSkipTakeSelectIterator<TSource, TResult>(_source, _selector, count, int.MaxValue);
}

public override Iterator<TResult> Take(int count)
{
Debug.Assert(count > 0);
return new IListSkipTakeSelectIterator<TSource, TResult>(_source, _selector, 0, count - 1);
}

public override bool MoveNext()
{
var source = _source;
int index = _state - 1;
if ((uint)index < (uint)source.Count)
{
_state++;
_current = _selector(source[index]);
return true;
}

Dispose();
return false;
}

public override TResult[] ToArray()
{
TResult[] array = new TResult[_source.Count];
for (int i = 0; i < array.Length; i++)
{
array[i] = _selector(_source[i]);
}
return array;
}

public override List<TResult> ToList()
{
List<TResult> list = new List<TResult>(_source.Count);
for (int i = 0; i < list.Count; i++)
{
list.Add(_selector(_source[i]));
}
return list;
}

private protected override Iterator<TResult> Clone()
=> new SizeOptIListSelectIterator<TSource, TResult>(_source, _selector);
}
}
}
9 changes: 8 additions & 1 deletion src/libraries/System.Linq/src/System/Linq/Select.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public static IEnumerable<TResult> Select<TSource, TResult>(
// don't need more code, just more data structures describing the new types).
if (IsSizeOptimized && typeof(TResult).IsValueType)
{
return new IEnumerableSelectIterator<TSource, TResult>(iterator, selector);
return source is IList<TSource> il
? new SizeOptIListSelectIterator<TSource, TResult>(il, selector)
: new IEnumerableSelectIterator<TSource, TResult>(iterator, selector);
}
else
{
Expand All @@ -42,6 +44,11 @@ public static IEnumerable<TResult> Select<TSource, TResult>(

if (source is IList<TSource> ilist)
{
if (IsSizeOptimized)
{
return new SizeOptIListSelectIterator<TSource, TResult>(ilist, selector);
}

if (source is TSource[] array)
{
if (array.Length == 0)
Expand Down
20 changes: 0 additions & 20 deletions src/libraries/System.Linq/src/System/Linq/Skip.SizeOpt.cs

This file was deleted.

4 changes: 2 additions & 2 deletions src/libraries/System.Linq/src/System/Linq/Skip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> sourc

count = 0;
}
else if (!IsSizeOptimized && source is Iterator<TSource> iterator)
else if (source is Iterator<TSource> iterator)
{
return iterator.Skip(count) ?? Empty<TSource>();
}

return IsSizeOptimized ? SizeOptimizedSkipIterator(source, count) : SpeedOptimizedSkipIterator(source, count);
return SpeedOptimizedSkipIterator(source, count);
}

public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
Expand Down
47 changes: 0 additions & 47 deletions src/libraries/System.Linq/src/System/Linq/Take.SizeOpt.cs

This file was deleted.

8 changes: 3 additions & 5 deletions src/libraries/System.Linq/src/System/Linq/Take.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> sourc
return [];
}

return IsSizeOptimized ? SizeOptimizedTakeIterator(source, count) : SpeedOptimizedTakeIterator(source, count);
return SpeedOptimizedTakeIterator(source, count);
}

/// <summary>Returns a specified range of contiguous elements from a sequence.</summary>
Expand Down Expand Up @@ -68,7 +68,7 @@ public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> sourc
return [];
}

return IsSizeOptimized ? SizeOptimizedTakeRangeIterator(source, startIndex, endIndex) : SpeedOptimizedTakeRangeIterator(source, startIndex, endIndex);
return SpeedOptimizedTakeRangeIterator(source, startIndex, endIndex);
}

return TakeRangeFromEndIterator(source, isStartIndexFromEnd, startIndex, isEndIndexFromEnd, endIndex);
Expand All @@ -94,9 +94,7 @@ private static IEnumerable<TSource> TakeRangeFromEndIterator<TSource>(IEnumerabl

if (startIndex < endIndex)
{
IEnumerable<TSource> rangeIterator = IsSizeOptimized
? SizeOptimizedTakeRangeIterator(source, startIndex, endIndex)
: SpeedOptimizedTakeRangeIterator(source, startIndex, endIndex);
IEnumerable<TSource> rangeIterator = SpeedOptimizedTakeRangeIterator(source, startIndex, endIndex);
foreach (TSource element in rangeIterator)
{
yield return element;
Expand Down
Loading
Loading