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
Evaluator allocation optimization #7056
Evaluator allocation optimization #7056
Conversation
|
Out of curiosity, instead of having the API allocate the list, why not pass a list to add to, to the API? That avoids all the intermediate collection allocations. |
src/Build/Evaluation/Evaluator.cs
Outdated
@@ -1428,7 +1428,7 @@ private void EvaluateImportElement(string directoryOfImportingFile, ProjectImpor | |||
{ | |||
using (_evaluationProfiler.TrackElement(importElement)) | |||
{ | |||
List<ProjectRootElement> importedProjectRootElements = ExpandAndLoadImports(directoryOfImportingFile, importElement, out var sdkResult); | |||
IEnumerable<ProjectRootElement> importedProjectRootElements = ExpandAndLoadImports(directoryOfImportingFile, importElement, out var sdkResult); | |||
|
|||
foreach (ProjectRootElement importedProjectRootElement in importedProjectRootElements) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the type of the variable to IEnumerable
has the unfortunate implication of regressing foreach
performance. Compare the IL code here:
When typed as IEnumerable
, List's enumerator is boxed (an extra allocation) and then accessed through the IEnumerator
interface (tiny bit slower than direct calls).
Can this optimization be implemented without changing the type, by using a special value of null
to represent an empty list for example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know about value type based enumerator for List<T>
and I was thinking about using null
to keep specific type. To be honest, I don't know why I rejected this idea so it worth to use it.
I will try to use ImmutableArray
and MoveToImmutable
as Drew mentioned. Maybe we will not need List
at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ladipro I changed the code to use null
s.
There are several ways to construct
If you are able to use |
@Therzok It was one of the solutions I proposed in the issue. The change would be more impactful, because for example |
@drewnoakes When I looked at the description for this method some time ago, I somehow fixed in my mind that it immediately creates new array for Builder. I checked the source and it seems I understood it wrong. I will update my branch with |
@drewnoakes I tried |
src/Build/Evaluation/Evaluator.cs
Outdated
if (projects?.Count > 0) | ||
{ | ||
projects = new List<ProjectRootElement>(projects); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
src/Build/Evaluation/Evaluator.cs
Outdated
if (sdkResult.AdditionalPaths != null) | ||
{ | ||
if (projects == null && sdkResult.AdditionalPaths.Count > 0) | ||
{ | ||
projects = new List<ProjectRootElement>(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
if (sdkResult.AdditionalPaths != null) | |
{ | |
if (projects == null && sdkResult.AdditionalPaths.Count > 0) | |
{ | |
projects = new List<ProjectRootElement>(); | |
} | |
if (sdkResult.AdditionalPaths?.Count > 0) | |
{ | |
projects ??= new List<ProjectRootElement>(); |
Or maybe even:
if (sdkResult.AdditionalPaths != null) | |
{ | |
if (projects == null && sdkResult.AdditionalPaths.Count > 0) | |
{ | |
projects = new List<ProjectRootElement>(); | |
} | |
if (sdkResult.AdditionalPaths?.Count > 0) | |
{ |
and new up the List
lazily when actually adding to it a few lines below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh boy! That's an embarrassing refactoring mistake. I'm starting to have selective blindness in this part of code. It's scary that all tests passed... It should be fixed now.
Fixes #6307
Context
Evaluator sometimes allocates
List<ProjectRootElement>
instances when it's not necessary.Changes Made
Collection allocation is postponed and empty
Enumerable
singleton is used to represent empty result.@drewnoakes I tried to use
ImmutableArray
, but it produced more allocations - builder is copying underlying array when creating instance of the immutable collection.Testing
I tracked
List<ProjectRootElement>
allocations while building simple solution with two empty C# projects (.NET Framework and Core). Number of allocated instances dropped from 264 to 178.Notes
This optimization looked like good first issue to solve, but complexity of the
Evaluator
is IMHO high and usingout
parameters for these collections makes it more difficult to track their flow :)