Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Reimplement LINQ ToList using SegmentedArrayBuilder to reduce allocations#104365

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
eiriktsarpalis merged 7 commits intodotnet:mainfromandrewjsaid:linq-tolist-stack
Jul 16, 2024

Conversation

@andrewjsaid
Copy link
Contributor

@andrewjsaidandrewjsaid commentedJul 3, 2024
edited
Loading

After#96570,ToArray() in many places is faster and has fewer allocations thatToList(). As aList is just a wrapper over an array, we can re-use most of the logic inSegmentedArrayBuilder to buildLists, too.

There were some more complex cases usingSegmentedArrayBuilder for exampleConcat where I left the code as-is. I can also experiment with performance numbers for those if it is requested.

Benchmarks

Enumerable_ToList changes have been reverted, they are not in current PR version

Int32Tests
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3810/23H2/2023Update/SunValley3)AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores.NET SDK 8.0.302  [Host]     : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2  Job-YPZLVD : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2  Job-OASBAG : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2LaunchCount=3
MethodCountMeanRatioAllocatedAlloc Ratio
Enumerable_ToListmain125.87 ns1.00112 B1.00
Enumerable_ToListpr128.49 ns1.10104 B0.93
IEnumerableSelectIterator_ToListmain146.05 ns1.00168 B1.00
IEnumerableSelectIterator_ToListpr148.76 ns1.06160 B0.95
IteratorSelectIterator_ToListmain155.84 ns1.00184 B1.00
IteratorSelectIterator_ToListpr160.04 ns1.08184 B1.00
OfTypeIterator_ToListmain161.88 ns1.00200 B1.00
OfTypeIterator_ToListpr165.08 ns1.05176 B0.88
SelectManySingleSelectorIterator_ToListmain162.16 ns1.00240 B1.00
SelectManySingleSelectorIterator_ToListpr167.54 ns1.08232 B0.97
IEnumerableSkipTakeIterator_ToLostmain141.68 ns1.00128 B1.00
IEnumerableSkipTakeIterator_ToLostpr143.83 ns1.05128 B1.00
IEnumerableWhereIterator_ToListmain142.11 ns1.00168 B1.00
IEnumerableWhereIterator_ToListpr142.63 ns1.01160 B0.95
ArrayWhereIterator_ToListmain138.62 ns1.00152 B1.00
ArrayWhereIterator_ToListpr145.68 ns1.18144 B0.95
IEnumerableWhereSelectIterator_ToListmain160.61 ns1.00232 B1.00
IEnumerableWhereSelectIterator_ToListpr165.19 ns1.10224 B0.97
ArrayWhereSelectIterator_ToListmain155.79 ns1.00208 B1.00
ArrayWhereSelectIterator_ToListpr160.01 ns1.07200 B0.96
Enumerable_ToListmain544.33 ns1.00168 B1.00
Enumerable_ToListpr532.99 ns0.74120 B0.71
IEnumerableSelectIterator_ToListmain565.96 ns1.00224 B1.00
IEnumerableSelectIterator_ToListpr556.28 ns0.85176 B0.79
IteratorSelectIterator_ToListmain573.91 ns1.00224 B1.00
IteratorSelectIterator_ToListpr577.94 ns1.05224 B1.00
OfTypeIterator_ToListmain5132.67 ns1.00384 B1.00
OfTypeIterator_ToListpr5115.63 ns0.87304 B0.79
SelectManySingleSelectorIterator_ToListmain5122.24 ns1.00296 B1.00
SelectManySingleSelectorIterator_ToListpr5106.91 ns0.88248 B0.84
IEnumerableSkipTakeIterator_ToLostmain556.19 ns1.00168 B1.00
IEnumerableSkipTakeIterator_ToLostpr558.61 ns1.04168 B1.00
IEnumerableWhereIterator_ToListmain562.12 ns1.00224 B1.00
IEnumerableWhereIterator_ToListpr550.66 ns0.81176 B0.79
ArrayWhereIterator_ToListmain556.66 ns1.00224 B1.00
ArrayWhereIterator_ToListpr549.61 ns0.88176 B0.79
IEnumerableWhereSelectIterator_ToListmain582.31 ns1.00288 B1.00
IEnumerableWhereSelectIterator_ToListpr572.20 ns0.88240 B0.83
ArrayWhereSelectIterator_ToListmain574.32 ns1.00280 B1.00
ArrayWhereSelectIterator_ToListpr562.94 ns0.85232 B0.83
Enumerable_ToListmain50167.20 ns1.00688 B1.00
Enumerable_ToListpr50118.14 ns0.71296 B0.43
IEnumerableSelectIterator_ToListmain50191.60 ns1.00744 B1.00
IEnumerableSelectIterator_ToListpr50171.93 ns0.90352 B0.47
IteratorSelectIterator_ToListmain50251.54 ns1.00800 B1.00
IteratorSelectIterator_ToListpr50231.18 ns0.92408 B0.51
OfTypeIterator_ToListmain50731.40 ns1.002432 B1.00
OfTypeIterator_ToListpr50734.97 ns1.011744 B0.72
SelectManySingleSelectorIterator_ToListmain50692.19 ns1.00816 B1.00
SelectManySingleSelectorIterator_ToListpr50656.03 ns0.95424 B0.52
IEnumerableSkipTakeIterator_ToLostmain50186.72 ns1.00744 B1.00
IEnumerableSkipTakeIterator_ToLostpr50161.33 ns0.86352 B0.47
IEnumerableWhereIterator_ToListmain50187.08 ns1.00744 B1.00
IEnumerableWhereIterator_ToListpr50164.58 ns0.88352 B0.47
ArrayWhereIterator_ToListmain50154.86 ns1.00920 B1.00
ArrayWhereIterator_ToListpr50141.83 ns0.92528 B0.57
IEnumerableWhereSelectIterator_ToListmain50209.25 ns1.00808 B1.00
IEnumerableWhereSelectIterator_ToListpr50186.49 ns0.89416 B0.51
ArrayWhereSelectIterator_ToListmain50171.83 ns1.00976 B1.00
ArrayWhereSelectIterator_ToListpr50145.71 ns0.85584 B0.60
ObjectTests
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3810/23H2/2023Update/SunValley3)AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores.NET SDK 8.0.302  [Host]     : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2  Job-YPZLVD : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2  Job-OASBAG : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2LaunchCount=3
MethodCountMeanRatioAllocatedAlloc Ratio
Enumerable_ToListmain141.15 ns1.00136 B1.00
Enumerable_ToListpr145.14 ns1.10112 B0.82
IEnumerableSelectIterator_ToListmain168.84 ns1.00192 B1.00
IEnumerableSelectIterator_ToListpr168.88 ns1.01168 B0.88
IteratorSelectIterator_ToListmain175.90 ns1.00192 B1.00
IteratorSelectIterator_ToListpr184.56 ns1.11192 B1.00
OfTypeIterator_ToListmain157.58 ns1.00184 B1.00
OfTypeIterator_ToListpr161.52 ns1.07160 B0.87
SelectManySingleSelectorIterator_ToListmain188.42 ns1.00264 B1.00
SelectManySingleSelectorIterator_ToListpr189.23 ns1.00240 B0.91
IEnumerableSkipTakeIterator_ToLostmain155.73 ns1.00136 B1.00
IEnumerableSkipTakeIterator_ToLostpr160.91 ns1.09136 B1.00
IEnumerableWhereIterator_ToListmain166.58 ns1.00192 B1.00
IEnumerableWhereIterator_ToListpr170.11 ns1.05168 B0.88
ArrayWhereIterator_ToListmain148.82 ns1.00168 B1.00
ArrayWhereIterator_ToListpr156.08 ns1.15144 B0.86
IEnumerableWhereSelectIterator_ToListmain187.87 ns1.00256 B1.00
IEnumerableWhereSelectIterator_ToListpr190.83 ns1.03232 B0.91
ArrayWhereSelectIterator_ToListmain170.01 ns1.00224 B1.00
ArrayWhereSelectIterator_ToListpr173.59 ns1.05200 B0.89
Enumerable_ToListmain584.91 ns1.00224 B1.00
Enumerable_ToListpr571.13 ns0.83144 B0.64
IEnumerableSelectIterator_ToListmain5168.50 ns1.00280 B1.00
IEnumerableSelectIterator_ToListpr5153.26 ns0.91200 B0.71
IteratorSelectIterator_ToListmain5121.77 ns1.00248 B1.00
IteratorSelectIterator_ToListpr5187.19 ns1.54248 B1.00
OfTypeIterator_ToListmain5101.74 ns1.00272 B1.00
OfTypeIterator_ToListpr588.26 ns0.87192 B0.71
SelectManySingleSelectorIterator_ToListmain5187.48 ns1.00352 B1.00
SelectManySingleSelectorIterator_ToListpr5153.98 ns0.82272 B0.77
IEnumerableSkipTakeIterator_ToLostmain591.36 ns1.00192 B1.00
IEnumerableSkipTakeIterator_ToLostpr594.57 ns1.04192 B1.00
IEnumerableWhereIterator_ToListmain5170.63 ns1.00280 B1.00
IEnumerableWhereIterator_ToListpr5154.21 ns0.90200 B0.71
ArrayWhereIterator_ToListmain5144.74 ns1.00288 B1.00
ArrayWhereIterator_ToListpr5135.21 ns0.94208 B0.72
IEnumerableWhereSelectIterator_ToListmain5191.12 ns1.00344 B1.00
IEnumerableWhereSelectIterator_ToListpr5176.70 ns0.93264 B0.77
ArrayWhereSelectIterator_ToListmain5166.66 ns1.00344 B1.00
ArrayWhereSelectIterator_ToListpr5149.40 ns0.90264 B0.77
Enumerable_ToListmain50452.81 ns1.001192 B1.00
Enumerable_ToListpr50429.92 ns0.94504 B0.42
IEnumerableSelectIterator_ToListmain50543.63 ns1.001248 B1.00
IEnumerableSelectIterator_ToListpr50552.12 ns1.02560 B0.45
IteratorSelectIterator_ToListmain50701.63 ns1.001304 B1.00
IteratorSelectIterator_ToListpr50707.15 ns1.01608 B0.47
OfTypeIterator_ToListmain50499.60 ns1.001240 B1.00
OfTypeIterator_ToListpr50501.70 ns1.00552 B0.45
SelectManySingleSelectorIterator_ToListmain501,267.24 ns1.001320 B1.00
SelectManySingleSelectorIterator_ToListpr501,033.94 ns0.82632 B0.48
IEnumerableSkipTakeIterator_ToLostmain50475.09 ns1.001248 B1.00
IEnumerableSkipTakeIterator_ToLostpr50483.19 ns1.02552 B0.44
IEnumerableWhereIterator_ToListmain50566.51 ns1.001248 B1.00
IEnumerableWhereIterator_ToListpr50559.30 ns0.99560 B0.45
ArrayWhereIterator_ToListmain50474.29 ns1.001616 B1.00
ArrayWhereIterator_ToListpr50544.54 ns1.15928 B0.57
IEnumerableWhereSelectIterator_ToListmain50581.70 ns1.001312 B1.00
IEnumerableWhereSelectIterator_ToListpr50599.43 ns1.03624 B0.48
ArrayWhereSelectIterator_ToListmain50494.47 ns1.001672 B1.00
ArrayWhereSelectIterator_ToListpr50499.22 ns1.01984 B0.59

Benchmark Code

Command Line
dotnet run-c Release`--corerun`     D:\Code\_forks\dotnet-runtime-base\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe`     D:\Code\_forks\dotnet-runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe`--launchCount3
Program.cs
usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Tests<>).Assembly).Run(args);publicpartialclassTests<T>whereT:new(){privateT_value=new();privateT[]_valueArr=[new()];privateIEnumerable<T>CreateEnumerable(){for(inti=0;i<Count;i++)yieldreturn_value;}privateIEnumerable<T>CreateArray(){varresult=newT[Count];for(inti=0;i<Count;i++){result[i]=_value;}returnresult;}[Params(1,5,50)]publicintCount{get;set;}[Benchmark]publicList<T>Enumerable_ToList()=>CreateEnumerable().ToList();[Benchmark]publicList<T>IEnumerableSelectIterator_ToList()=>AssertType(CreateEnumerable().Select(i=>i),"IEnumerableSelectIterator`2").ToList();[Benchmark]publicList<T>IteratorSelectIterator_ToList()=>AssertType(CreateEnumerable().Skip(1).Select(i=>i),"IteratorSelectIterator`2").ToList();[Benchmark]publicList<object>OfTypeIterator_ToList()=>AssertType(CreateEnumerable().OfType<object>(),"OfTypeIterator`1").ToList();[Benchmark]publicList<T>SelectManySingleSelectorIterator_ToList()=>AssertType(CreateEnumerable().SelectMany(i=>_valueArr),"SelectManySingleSelectorIterator`2").ToList();[Benchmark]publicList<T>IEnumerableSkipTakeIterator_ToLost()=>AssertType(CreateEnumerable().Skip(1),"IEnumerableSkipTakeIterator`1").ToList();[Benchmark]publicList<T>IEnumerableWhereIterator_ToList()=>AssertType(CreateEnumerable().Where(_=>true),"IEnumerableWhereIterator`1").ToList();[Benchmark]publicList<T>ArrayWhereIterator_ToList()=>AssertType(CreateArray().Where(_=>true),"ArrayWhereIterator`1").ToList();[Benchmark]publicList<T>IEnumerableWhereSelectIterator_ToList()=>AssertType(CreateEnumerable().Where(_=>true).Select(i=>i),"IEnumerableWhereSelectIterator`2").ToList();[Benchmark]publicList<T>ArrayWhereSelectIterator_ToList()=>AssertType(CreateArray().Where(_=>true).Select(i=>i),"ArrayWhereSelectIterator`2").ToList();privatestaticTIteratorAssertType<TIterator>(TIteratorvalue,stringtype){if(value!.GetType().Nameis{}actual&&actual!=type){thrownewInvalidOperationException($"Expected{type} got{actual}");}returnvalue;}}[HideColumns("Error","StdDev","Median","RatioSD","Job")][MemoryDiagnoser(false)]publicclassInt32Tests:Tests<int>{}[HideColumns("Error","StdDev","Median","RatioSD","Job")][MemoryDiagnoser(false)]publicclassObjectTests:Tests<object>{}

Performance Analysis

In all cases allocations are down and sometimes significantly so. As the size of the collection increases so do the savings due to reduced number of "unnecessary" array copies as the list grows.

Speed is more of a trade-off. For lower counts the PR is actually slower whereas for higher counts it is faster, again due to less time copying data around.

However this must be seen in context; we are trading off nanoseconds. In situations where nanoseconds of individual LINQ operations matter, it would probably be recommended to write the loops by hand anyway. On this basis I would say that creating less garbage to collect would probably be better for the overall system performance than the slower code for the Count less than 5 case.

@ghostghost added the area-System.Linq labelJul 3, 2024
@dotnet-policy-servicedotnet-policy-servicebot added the community-contributionIndicates that the PR has been added by a community member labelJul 3, 2024
@dotnet-policy-service
Copy link
Contributor

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

Copy link
Member

@stephentoubstephentoub left a comment

Choose a reason for hiding this comment

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

Thanks.

@eiriktsarpaliseiriktsarpalis added the tenet-performancePerformance related issue labelJul 16, 2024
Copy link
Member

@eiriktsarpaliseiriktsarpalis left a comment

Choose a reason for hiding this comment

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

Thanks!

@eiriktsarpaliseiriktsarpalis merged commit3922062 intodotnet:mainJul 16, 2024
@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsAug 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.

Reviewers

@eiriktsarpaliseiriktsarpaliseiriktsarpalis approved these changes

@stephentoubstephentoubAwaiting requested review from stephentoub

Assignees

No one assigned

Labels

area-System.Linqcommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis

[8]ページ先頭

©2009-2025 Movatter.jp