Avoid OfTypeIterators when enumerating project xml children#8847
Avoid OfTypeIterators when enumerating project xml children#8847rainersigwald merged 1 commit intodotnet:vs17.7from
Conversation
There was a problem hiding this comment.
Maybe I'm wrong here, but I guess all of the changes that cast GetChildrenOfType to ICollection do nothing here, because you cast you collection to ICollection and so the consumer of this code iterating through ICollection interface won't call a special GetEnumerator method that returns your struct, but rather will call a method on the interface that still would box the iterator.
There was a problem hiding this comment.
Bah, I think you're right. This would only save on the OfTypeIterators. Maybe still substantial enough to be worth it, but it's less compelling now. Might need to actually compare and gather data.
There was a problem hiding this comment.
For public consumers of these types, you can fix this with a bit of a hack which we use around the tree in situations where we can't change the public API.
Basically if you make the underlying ICollection a known public type, external consumers (like CPS, AnyCode, etc) can cast to and sniff for said type and avoid the boxing by calling the direct enumerator.
There was a problem hiding this comment.
Gotcha. I'm not sure we can make it a known public type very easily though since the existing impl is lazy. It's effectively a linked list "view" on the underlying Xml object model, and while the enumerator may box, it's not knowable whether allocating the full collection would be beneficial or not.
Consider a naive consumer who just enumerates. With these changes, it would allocate the collection (just a wrapper) and box the enumerator. If we returned a well-known collection (let's say List), we'd have to enumerate the full collection and populate the whole collection, which takes more memory due to the underlying storage (array).
src/Build/Definition/Toolset.cs
Outdated
There was a problem hiding this comment.
I guess the only benefits you're getting is here. But I'm curious if you can avoid the collection allocation here as well by having a property 'ChildrenEnumerator' that will return your struct enumerator?
There was a problem hiding this comment.
Renamed ChildrenInternal -> ChildrenEnumerable for clarity
There was a problem hiding this comment.
lol, due to the hidden copy that C# performs. :)
There was a problem hiding this comment.
I don't know if MSBuild has the ability to ban APIs but I would consider banning usage of Children inside of MSBuild itself to avoid accidentally opt'ing into the boxing again. Can't obsolete because that would affect external consumers.
There was a problem hiding this comment.
Hmm, interesting idea. It seems to work except I'm unsure how to handle this for UTs since it's a internal member and not all UTs have visibility. I could InternalsVisibleTo, but not sure if that's desirable here or not (MSBuild team feel free to express an opinion)
Note to self in case it's decided to do this:
P:Microsoft.Build.Construction.ProjectElementContainer.Children;Use ChildrenEnumerable instead to avoid boxing
There was a problem hiding this comment.
We can skip the ban enforcement in UTs. I'll push a commit with that shortly.
|
From the thread which initiated this: Initial:
And after applying the changes:
So still work to do, but this incrementally helps. |
There was a problem hiding this comment.
We can skip the ban enforcement in UTs. I'll push a commit with that shortly.
In a recent profile of a graph construction, it was observed that a large amount of boxing was happening for ProjectElementSiblingEnumerable. This change simplifies how xml children are enumerated by adding an internal ChildrenEnumerable property which directly exposes the ProjectElementSiblingEnumerable which should avoid boxing, at least in some code paths (the public API makes it hard to avoid everywhere...). Additionally, a very common usage of enumerating children was to do Children.OfType<T> and wrap it in a ReadOnlyCollection<T>, so I introduced a GetChildrenOfType (and GetChildrenReversedOfType) method which exposes an ICollection<T> which does the same thing but without the boxing of ProjectElementSiblingEnumerable and without the OfType class. It's just 1 collection allocation.
0b79dfe to
4f2a020
Compare
Summary
Performance improvement with noticeable impact when using static graph in large repos.
Customer Impact
~10% reduction in static graph construction time in a specific large repo.
Regression?
No.
Testing
Automated tests, manual runs on reporting repo showing noticeable improvement.
Risk
Low.
Details
Avoid OfTypeIterators when enumerating project xml children
In a recent profile of a graph construction, it was observed that a large amount of boxing was happening for
ProjectElementSiblingEnumerable. This change simplifies how xml children are enumerated by adding an internalChildrenEnumerableproperty which directly exposes theProjectElementSiblingEnumerablewhich should avoid boxing, at least in some code paths (the public API makes it hard to avoid everywhere...).Additionally, a very common usage of enumerating children was to do
Children.OfType<T>and wrap it in aReadOnlyCollection<T>, so I introduced aGetChildrenOfType(andGetChildrenReversedOfType) method which exposes anICollection<T>which does the same thing but without the boxing ofProjectElementSiblingEnumerableand without theOfTypeclass. It's just 1 collection allocation.