Skip to content

Conversation

@prozolic
Copy link
Contributor

Summary

This PR adds a fast path in IEnumerableSkipTakeIterator.TryGetLast. When source is an Iterator and GetCount(onlyIfCheap: true) returns a valid count (not -1), check if count <= _minIndexInclusive. In this case, immediately return with found=false.

Benchmark

| Method                    | Toolchain         | Mean     | Error    | StdDev   | Median   | Code Size | Gen0   | Allocated |
|-------------------------- |------------------ |---------:|---------:|---------:|---------:|----------:|-------:|----------:|
| LastOrDefault_PrependSkip | \main\corerun.exe | 54.05 ns | 4.295 ns | 4.774 ns | 53.12 ns |   2,616 B | 0.0180 |     152 B |
| LastOrDefault_PrependSkip | \pr\corerun.exe   | 25.75 ns | 0.948 ns | 1.014 ns | 25.67 ns |   1,777 B | 0.0181 |     152 B |
|                           |                   |          |          |          |          |           |        |           |
| LastOrDefault_AppendSkip  | \main\corerun.exe | 54.89 ns | 3.638 ns | 4.190 ns | 53.72 ns |   2,603 B | 0.0181 |     152 B |
| LastOrDefault_AppendSkip  | \pr\corerun.exe   | 25.78 ns | 1.001 ns | 1.112 ns | 25.53 ns |   1,770 B | 0.0181 |     152 B |
|                           |                   |          |          |          |          |           |        |           |
| LastOrDefault_ConcatSkip  | \main\corerun.exe | 85.73 ns | 4.710 ns | 5.424 ns | 87.06 ns |   4,306 B | 0.0255 |     216 B |
| LastOrDefault_ConcatSkip  | \pr\corerun.exe   | 41.26 ns | 2.351 ns | 2.613 ns | 40.88 ns |   2,089 B | 0.0220 |     184 B |
    [MemoryDiagnoser]
    [BenchmarkCategory(Categories.Libraries, Categories.LINQ)]
    public class SkipTakeTryGetLastBenchmarks
    {
        [Benchmark]
        public int LastOrDefault_PrependSkip()
        {
            return Enumerable.Range(0, 4).Prepend(4).Skip(5).LastOrDefault();
        }

        [Benchmark]
        public int LastOrDefault_AppendSkip()
        {
            return Enumerable.Range(0, 4).Append(4).Skip(5).LastOrDefault();
        }

        [Benchmark]
        public int LastOrDefault_ConcatSkip()
        {
            return Enumerable.Range(0, 3).Concat(new []{ 3, 4 }).Skip(5).LastOrDefault();
        }
    }

When source is an Iterator and GetCount(onlyIfCheap: true) returns a
valid count (not -1), check if count <= _minIndexInclusive. In this
case, immediately return with found=false.
Copilot AI review requested due to automatic review settings January 17, 2026 12:02
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jan 17, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 17, 2026
Copy link
Contributor

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 adds a performance optimization to IEnumerableSkipTakeIterator.TryGetLast by introducing an early return path when the source iterator can cheaply determine that there are no elements after skipping.

Changes:

  • Modified the condition from count > _minIndexInclusive to count != -1 to enter the fast path whenever a cheap count is available
  • Added an early return when count <= _minIndexInclusive, avoiding enumeration when there are no elements to return
  • Performance improvement demonstrated via benchmarks: ~2x faster for scenarios like Prepend().Skip(), Append().Skip(), and Concat().Skip() when skipping beyond the available elements

@huoyaoyuan huoyaoyuan added area-System.Linq and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jan 17, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-linq
See info in area-owners.md if you want to be subscribed.

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.

Looks reasonable. Thanks!

Co-authored-by: Stephen Toub <stoub@microsoft.com>
@stephentoub stephentoub merged commit 6afecd4 into dotnet:main Jan 18, 2026
85 of 89 checks passed
@prozolic prozolic deleted the trygetlast branch January 18, 2026 17:00
rosebyte pushed a commit that referenced this pull request Jan 19, 2026
This PR adds a fast path in `IEnumerableSkipTakeIterator.TryGetLast`.
When source is an Iterator and `GetCount(onlyIfCheap: true)` returns a
valid count (not -1), check if count <= _minIndexInclusive. In this
case, immediately return with found=false.

---------

Co-authored-by: Stephen Toub <stoub@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Linq community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants