Skip to content
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

Conversation

ToddGrun
Copy link
Contributor

This code realized an ImmutableArray before from an ArrayBuilder. This immutablearray was only used within the scope of SynchronizeAssetsAsync.

*** Prior allocations from speedometer:
image

@sharwell sharwell changed the title WIP: Reduce allocations under RemoveAssetProvider.SynchronizeAssetsAsync WIP: Reduce allocations under RemoteAssetProvider.SynchronizeAssetsAsync Feb 22, 2024
@@ -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)
Copy link
Member

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.

Suggested change
public static PooledObject<HashSet<Checksum>> CreateChecksumSet(SegmentedList<Checksum> checksums)
public static PooledObject<HashSet<Checksum>> CreateChecksumSet(ImmutableSegmentedList<Checksum> checksums)

Copy link
Contributor Author

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?

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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();
Copy link
Member

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);
}

Copy link
Contributor Author

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?

Copy link
Member

@sharwell sharwell Feb 22, 2024

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Feb 23, 2024
@ToddGrun
Copy link
Contributor Author

This looks effective from the speedometer run here (CompletionTest.Completion/9990.Totals/CLR_BytesAllocates_NonDevenv)

Name                                   	Exc %	       Exc
Type Microsoft.CodeAnalysis.Checksum[] 	  0.1	 4,900,776

No HashSet<Checksum> appears in the profile.

Going to elevate out of draft status

@ToddGrun ToddGrun marked this pull request as ready for review February 23, 2024 03:55
@ToddGrun ToddGrun requested a review from a team as a code owner February 23, 2024 03:55
@ToddGrun
Copy link
Contributor Author

@sharwell, @CyrusNajmabadi -- I've moved this out of draft status as it shows promise, ptal

@ToddGrun ToddGrun changed the title WIP: Reduce allocations under RemoteAssetProvider.SynchronizeAssetsAsync Reduce allocations under RemoteAssetProvider.SynchronizeAssetsAsync Feb 23, 2024
Copy link
Member

@sharwell sharwell left a 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

@ToddGrun
Copy link
Contributor Author

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:

  1. Added msgpack serialization for ImmutableSegmentedList
  2. Added ImmutableCollectionsMarshal equivalent for ImmutableSegmentedList

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.

@ToddGrun
Copy link
Contributor Author

ToddGrun commented Mar 5, 2024

Requesting changes pending investigation on not losing argument immutability

@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)

@ToddGrun
Copy link
Contributor Author

ToddGrun commented Mar 6, 2024

waiting on #72395 as it provides the ImmutableCollectionsMarshal this PR wants

Comment on lines +78 to +84
public static class ImmutableCollectionsMarshal
{
public static ImmutableSegmentedList<T> ToImmutableSegmentedList(SegmentedList<T> list)
{
return new ImmutableSegmentedList<T>(list);
}
}
Copy link
Member

@sharwell sharwell Mar 19, 2024

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

Comment on lines +24 to +25
// TODO: Should there be a CreateBuilder that takes in an initial capacity
// similar to what ImmutableArray allows?
Copy link
Member

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.

Copy link
Member

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.

@ToddGrun ToddGrun closed this Apr 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants