Skip to content

Conversation

agocke
Copy link
Member

@agocke agocke commented Jul 29, 2025

This lets us keep some of the constant-time indexing advantages of the IList iterator, without the GVM overhead of Select. There is a small size increase here, but nowhere near the cost of the GVM.

In a pathological generated example for GVMs the cost was:

  1. .NET 9: 12 MB
  2. .NET 10 w/out this change: 2.2 MB
  3. .NET 10 w/ this change: 2.3 MB

In a real-world example (AzureMCP), the size attributed to System.Linq was:

  1. .NET 9: 1.2 MB
  2. .NET 10 w/out this change: 340 KB
  3. .NET 10 w/ this change: 430 KB

This seems like a good tradeoff. We mostly keep the algorithmic complexity the same across the size/speed-opt versions, and just tradeoff on the margins. We could probably continue to improve this in the future.

Fixes #115033

This lets us keep some of the constant-time indexing advantages of the
IList iterator, without the GVM overhead of Select. There is a small
size increase here, but nowhere near the cost of the GVM.

In a pathological generated example for GVMs the cost was:

  1. .NET 9: 12 MB
  2. .NET 10 w/out this change: 2.2 MB
  3. .NET 10 w/ this change: 2.3 MB

In a real-world example (AzureMCP), the size attributed to System.Linq
was:

  1. .NET 9: 1.2 MB
  2. .NET 10 w/out this change: 340 KB
  3. .NET 10 w/ this change: 430 KB

This seems like a good tradeoff. We mostly keep the algorithmic
complexity the same across the size/speed-opt versions, and just
tradeoff on the margins. We could probably continue to improve this in
the future.
@agocke agocke marked this pull request as ready for review July 29, 2025 20:32
@Copilot Copilot AI review requested due to automatic review settings July 29, 2025 20:32
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a size-optimized IList-based Select iterator to reduce binary size while maintaining constant-time indexing advantages. The change removes the size-optimized variants of Take and Skip operations in favor of a more targeted optimization for Select operations on IList collections.

Key changes:

  • Introduces SizeOptIListSelectIterator for size-optimized Select operations on IList collections
  • Removes size-optimized Take and Skip iterator implementations
  • Updates Take and Skip to always use speed-optimized variants

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/libraries/System.Linq/src/System/Linq/Take.cs Removes conditional size optimization, always uses speed-optimized iterator
src/libraries/System.Linq/src/System/Linq/Take.SizeOpt.cs Removes size-optimized Take iterator implementation
src/libraries/System.Linq/src/System/Linq/Skip.cs Removes conditional size optimization, always uses speed-optimized iterator
src/libraries/System.Linq/src/System/Linq/Skip.SizeOpt.cs Completely removes file containing size-optimized Skip iterator
src/libraries/System.Linq/src/System/Linq/Select.cs Adds IList detection and uses new size-optimized IList iterator
src/libraries/System.Linq/src/System/Linq/Iterator.SizeOpt.cs New file implementing size-optimized IList Select iterator
src/libraries/System.Linq/src/System.Linq.csproj Updates project to include new Iterator.SizeOpt.cs and remove Skip.SizeOpt.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
@agocke
Copy link
Member Author

agocke commented Aug 1, 2025

/azp run runtime-nativeaot-outerloop

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Co-authored-by: Stephen Toub <stoub@microsoft.com>
@MichalStrehovsky
Copy link
Member

/azp run runtime-nativeaot-outerloop

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@agocke
Copy link
Member Author

agocke commented Aug 15, 2025

@stephentoub Would you mind taking another look at this? I think it's in a good spot.

Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from a LINQ perspective if you're happy with it from a size perspective.

@MichalStrehovsky
Copy link
Member

From native AOT perspective, the size optimized LINQ switch still provides enough benefit over non-size optimized to have a standing, so native AOT size LGTM if this provides the throughput improvements that are needed.

@agocke
Copy link
Member Author

agocke commented Aug 15, 2025

Thanks everyone! It was a little hairy, but the final result looks pretty good to me.

the throughput improvements that are needed

I believe this is in a good place. There are some corner cases (set union) where there are still perf differences, but this heavily narrows the gap.

@agocke
Copy link
Member Author

agocke commented Aug 16, 2025

/ba-g all failures known

@agocke agocke disabled auto-merge August 16, 2025 16:33
@agocke agocke merged commit 58d1c2e into dotnet:main Aug 16, 2025
84 of 87 checks passed
@agocke agocke deleted the linq-ilist branch August 16, 2025 16:36

public SizeOptIListWhereIterator(IList<TSource> source, Func<TSource, bool> predicate)
{
Debug.Assert(source is not null && source.Count > 0);
Copy link
Contributor

@skyoxZ skyoxZ Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the code of class SizeOptIListWhereIterator was copied from ListWhereIterator, but here an extra assertion of source.Count > 0 was added? Empty IList is not filtered out by caller.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Yes, this was a copy-paste error. However, the code does correctly handle this case -- only the assert is incorrect. I'll fix it up in a follow-up PR.

@agocke
Copy link
Member Author

agocke commented Aug 18, 2025

/backport to release/10.0-rc1

Copy link
Contributor

Started backporting to release/10.0-rc1: https://github.com/dotnet/runtime/actions/runs/17031246103

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Linq size-reduction Issues impacting final app size primary for size sensitive workloads
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Massive performance regression .NET 9 vs .NET 10
6 participants