Skip to content

Commit 5a98024

Browse files
authored
Reduce allocations again in ComputeReverseReferencesMap (#76417)
* Reduce allocations again in ComputeReverseReferencesMap Speedometer results from the last PR didn't improve allocations in the ProjectDependencyGraph as much as I had hoped. This PR instead changes ComputeReverseReferencesMap to use pooled data structures and ImmutableHashSet.Builder to build up it's final result. The speedometer test is still showing about 100 MB of allocations in this method during typing, my hope is that this should cut that in about half. Will create a test insertion for validation.
1 parent bf617ef commit 5a98024

File tree

1 file changed

+29
-6
lines changed

1 file changed

+29
-6
lines changed

src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Diagnostics;
99
using System.Linq;
1010
using System.Threading;
11+
using Microsoft.CodeAnalysis.PooledObjects;
1112
using Roslyn.Utilities;
1213

1314
namespace Microsoft.CodeAnalysis;
@@ -55,6 +56,9 @@ public partial class ProjectDependencyGraph
5556
private ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> _transitiveReferencesMap;
5657
private ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> _reverseTransitiveReferencesMap;
5758

59+
// Pool of ImmutableHashSet builders used in ComputeReverseReferencesMap to avoid temporary HashSet allocations.
60+
private static readonly ObjectPool<ImmutableHashSet<ProjectId>.Builder> s_reverseReferencesBuilderPool = new(static () => ImmutableHashSet.CreateBuilder<ProjectId>(), size: 256);
61+
5862
/// <remarks>
5963
/// Intentionally created with a null reverseReferencesMap. Doing so indicates _lazyReverseReferencesMap
6064
/// shouldn't be calculated until reverse reference information is requested. Once this information
@@ -209,17 +213,36 @@ private ImmutableHashSet<ProjectId> GetProjectsThatDirectlyDependOnThisProject_N
209213

210214
private ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> ComputeReverseReferencesMap()
211215
{
212-
var reverseReferencesMap = new Dictionary<ProjectId, HashSet<ProjectId>>();
213-
216+
using var _1 = PooledDictionary<ProjectId, ImmutableHashSet<ProjectId>.Builder>.GetInstance(out var reverseReferencesMapBuilders);
214217
foreach (var (projectId, references) in _referencesMap)
215218
{
216219
foreach (var referencedId in references)
217-
reverseReferencesMap.MultiAdd(referencedId, projectId);
220+
{
221+
if (!reverseReferencesMapBuilders.TryGetValue(referencedId, out var builder))
222+
{
223+
builder = s_reverseReferencesBuilderPool.Allocate();
224+
reverseReferencesMapBuilders.Add(referencedId, builder);
225+
}
226+
227+
builder.Add(projectId);
228+
}
229+
}
230+
231+
// Convert all the populated ImmutableHashSet.Builder objects to ImmutableHashSets
232+
var reverseReferencesBuilder = ImmutableDictionary.CreateBuilder<ProjectId, ImmutableHashSet<ProjectId>>();
233+
foreach (var (projectId, builder) in reverseReferencesMapBuilders)
234+
{
235+
// Realize an ImmutableHashSet from the builder
236+
var reverseReferencesForProject = builder.ToImmutableHashSet();
237+
238+
// Clear out the builder and release it back to the pool
239+
builder.Clear();
240+
s_reverseReferencesBuilderPool.Free(builder);
241+
242+
reverseReferencesBuilder.Add(projectId, reverseReferencesForProject);
218243
}
219244

220-
return reverseReferencesMap
221-
.Select(kvp => KeyValuePairUtil.Create(kvp.Key, kvp.Value.ToImmutableHashSet()))
222-
.ToImmutableDictionary();
245+
return reverseReferencesBuilder.ToImmutable();
223246
}
224247

225248
/// <summary>

0 commit comments

Comments
 (0)