-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Reduce allocations under RemoteAssetProvider.SynchronizeAssetsAsync #72229
Reduce allocations under RemoteAssetProvider.SynchronizeAssetsAsync #72229
Conversation
@@ -15,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Serialization | |||
/// </summary> | |||
internal static class Creator | |||
{ | |||
public static PooledObject<HashSet<Checksum>> CreateChecksumSet(ImmutableArray<Checksum> checksums) | |||
public static PooledObject<HashSet<Checksum>> CreateChecksumSet(SegmentedList<Checksum> checksums) |
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.
We don't want a mutable type here.
public static PooledObject<HashSet<Checksum>> CreateChecksumSet(SegmentedList<Checksum> checksums) | |
public static PooledObject<HashSet<Checksum>> CreateChecksumSet(ImmutableSegmentedList<Checksum> checksums) |
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'd love for this to be an immutable type, but I don't see how to get one down to this level without allocating up in SynchronizeAssetsAsync. Maybe I'm missing a trick?
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.
@ToddGrun what about, as an alternative, a readonly struct wrapper around SegmentedList?
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'm not clear on what that would gain. Yes, the wrapper would be immutable, but I assume it would give access to the mutable structure. I don't mind the current approach, especially since it shows promising allocation results, but am willing to consider other approaches.
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.
a readonly struct wrapper around SegmentedList?
ImmutableSegmentedList<T>
is exactly that.
@@ -118,28 +122,29 @@ public async ValueTask SynchronizeProjectAssetsAsync(ProjectStateChecksums proje | |||
|
|||
using (Logger.LogBlock(FunctionId.AssetService_SynchronizeAssetsAsync, Checksum.GetChecksumsLogInfo, checksums, cancellationToken)) | |||
{ | |||
using var _1 = ArrayBuilder<Checksum>.GetInstance(checksums.Count, out var missingChecksums); | |||
using var missingChecksumsObject = s_checksumListPool.GetPooledObject(); |
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.
This intermediate collection is completely unnecessary. Just use the checksums
argument directly:
// Remove all values from 'checksums' where the asset cache already contains a value
checksums.RemoveWhere(checksum => _assetCache.TryGetAsset<object>(checksum, out _));
This can be further optimized by defining PooledDelegates.GetPooledPredicate
, if necessary:
// Remove all values from 'checksums' where the asset cache already contains a value
using (var _1 = PooledDelegates.GetPooledPredicate<Checksum, AssetProvider>(
static (checksum, arg) => arg._assetCache.TryGetAsset<object>(checksum, out _),
this,
out var predicate))
{
checksums.RemoveWhere(predicate);
}
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 don't think I'm following this suggestion.
Modifying that hashset in place requires knowledge that the caller is ok with that. It might be ok, but I'd need to verify that.
Assuming that were ok, then I'd need to pass that HashSet down and change all the calls to take in a HashSet instead of a SegmentedList, right?
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.
Modifying that hashset in place requires knowledge that the caller is ok with that. It might be ok, but I'd need to verify that.
I did verify it, but we'd want a way to make it apparent to the callers. One option is to make the caller responsible for filtering the list, e.g. by checking the asset cache directly in the code that is adding items to the set.
Assuming that were ok, then I'd need to pass that HashSet down and change all the calls to take in a HashSet instead of a SegmentedList, right?
That's one option. We could also create an ImmutableArray
from it directly by calling ImmutableArray.CreateRange<T>
, without going through the builder pool.
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.
The intent of this PR was to investigate removing the allocation of creating that ImmutableArray. If we need that data structure to be passed down as immutable, I should just close out this PR. But, from what I saw, it didn't need to be immutable, it was just a nice to have. This is an investigation into whether removing that nice to have could have a positive perf impact without too much ugliness in the code.
As for the suggestion to modify the HashSet in-place, I guess I'm still not seeing the value. Perhaps the speedometer profile with this change will show that's an issue, but I'd prefer to not make that change unless it's needed as it does change the semantics around the hashset passed into this method.
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.
as these are all internal components, without a surface area exposed to other features, i would be fine with this not being immutable, esp if it cuts down on churn.
This looks effective from the speedometer run here (CompletionTest.Completion/9990.Totals/CLR_BytesAllocates_NonDevenv)
No HashSet<Checksum> appears in the profile. Going to elevate out of draft status |
@sharwell, @CyrusNajmabadi -- I've moved this out of draft status as it shows promise, ptal |
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.
Requesting changes pending investigation on not losing argument immutability
Would like to ping @CyrusNajmabadi and @sharwell for preferences on how to proceed. I personally don't mind sending the mutable types through the callstack as all the bits they proceed through are internal. As I know there is concern with that approach, I investigated what would be needed to make them immutable (ImmutableSegmentedList instead of ImmutableArray). It can be done (branch here), but it's a bit more effort:
I'm not in love with these changes, but I couldn't come up with an approach for sending an immutabletype through without allocations otherwise. |
…nc_Allocations_ImmutableCollectionsMarshal Dev/toddgrun/synchronize assets async allocations immutable collections marshal
@sharwell -- I'd like to keep this moving along. Do you think the ValueBuilder PR will go in sometime soon, or should I plan on making progress on this PR without that? I'm mostly interested in the ImmutableCollectionsMarshal equivalent that this PR created a rudimentary version of. (I'm also intereseted in the ImmutableSegmentedDictionary.AddRange optimization that I merged 72220 without) |
waiting on #72395 as it provides the ImmutableCollectionsMarshal this PR wants |
public static class ImmutableCollectionsMarshal | ||
{ | ||
public static ImmutableSegmentedList<T> ToImmutableSegmentedList(SegmentedList<T> list) | ||
{ | ||
return new ImmutableSegmentedList<T>(list); | ||
} | ||
} |
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.
❗ This can be removed now
// TODO: Should there be a CreateBuilder that takes in an initial capacity | ||
// similar to what ImmutableArray allows? |
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.
Yes, there should. In the meantime, you can use SegmentedCollectionsMarshal.AsImmutableSegmentedList
if you want.
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 have a branch ready to submit for this as soon as #62193 merges.
This code realized an ImmutableArray before from an ArrayBuilder. This immutablearray was only used within the scope of SynchronizeAssetsAsync.
*** Prior allocations from speedometer:
