Skip to content

Commit e323999

Browse files
authored
Reduce memory allocations when creating SourceRepository instances (#7126)
1 parent 1a090ec commit e323999

2 files changed

Lines changed: 50 additions & 19 deletions

File tree

src/NuGet.Core/NuGet.Protocol/SourceRepository.cs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ namespace NuGet.Protocol.Core.Types
1818
/// </summary>
1919
public class SourceRepository
2020
{
21-
private readonly Dictionary<Type, INuGetResourceProvider[]> _providerCache;
21+
internal const int ProviderCacheTypes = 25;
22+
private readonly Dictionary<Type, IReadOnlyList<INuGetResourceProvider>> _providerCache;
2223
private readonly PackageSource _source;
2324

2425
/// <summary>
@@ -150,12 +151,13 @@ public virtual async Task<T> GetResourceAsync<T>() where T : class, INuGetResour
150151
public virtual async Task<T> GetResourceAsync<T>(CancellationToken token) where T : class, INuGetResource
151152
{
152153
var resourceType = typeof(T);
153-
INuGetResourceProvider[] possible = null;
154+
IReadOnlyList<INuGetResourceProvider> possible;
154155

155156
if (_providerCache.TryGetValue(resourceType, out possible))
156157
{
157-
foreach (var provider in possible)
158+
for (int i = 0; i < possible.Count; i++)
158159
{
160+
var provider = possible[i];
159161
var result = await provider.TryCreate(this, token);
160162
if (result.Item1)
161163
{
@@ -172,47 +174,59 @@ public virtual async Task<T> GetResourceAsync<T>(CancellationToken token) where
172174
/// </summary>
173175
/// <param name="providers"></param>
174176
/// <returns></returns>
175-
private static Dictionary<Type, INuGetResourceProvider[]> Init(IEnumerable<Lazy<INuGetResourceProvider>> providers)
177+
private static Dictionary<Type, IReadOnlyList<INuGetResourceProvider>> Init(IEnumerable<Lazy<INuGetResourceProvider>> providers)
176178
{
177-
var cache = new Dictionary<Type, INuGetResourceProvider[]>();
179+
var cache = new Dictionary<Type, IReadOnlyList<INuGetResourceProvider>>(ProviderCacheTypes);
178180

179181
foreach (var group in providers.GroupBy(p => p.Value.ResourceType))
180182
{
181-
cache.Add(group.Key, Sort(group).ToArray());
183+
cache.Add(group.Key, Sort(group));
182184
}
183185

184186
return cache;
185187
}
186188

187-
private static INuGetResourceProvider[]
189+
private static IReadOnlyList<INuGetResourceProvider>
188190
Sort(IEnumerable<Lazy<INuGetResourceProvider>> group)
189191
{
192+
var items = new List<INuGetResourceProvider>(group.Count());
193+
foreach (var lazy in group)
194+
{
195+
items.Add(lazy.Value);
196+
}
197+
190198
// initial ordering to help make this deterministic
191-
var items = new List<INuGetResourceProvider>(
192-
group.Select(e => e.Value).OrderBy(e => e.Name).ThenBy(e => e.After.Count()).ThenBy(e => e.Before.Count()));
199+
items.Sort((a, b) =>
200+
{
201+
int cmp = StringComparer.Ordinal.Compare(a.Name, b.Name);
202+
if (cmp != 0) return cmp;
203+
cmp = a.After.Count().CompareTo(b.After.Count());
204+
if (cmp != 0) return cmp;
205+
return a.Before.Count().CompareTo(b.Before.Count());
206+
});
193207

194208
var comparer = ProviderComparer.Instance;
195209

196-
var ordered = new Queue<INuGetResourceProvider>();
197-
198210
// List.Sort does not work when lists have unsolvable gaps, which can occur here
199-
while (items.Count > 0)
211+
for (int start = 0; start < items.Count - 1; start++)
200212
{
201-
var best = items[0];
213+
int bestIndex = start;
202214

203-
for (var i = 1; i < items.Count; i++)
215+
for (int i = start + 1; i < items.Count; i++)
204216
{
205-
if (comparer.Compare(items[i], best) < 0)
217+
if (comparer.Compare(items[i], items[bestIndex]) < 0)
206218
{
207-
best = items[i];
219+
bestIndex = i;
208220
}
209221
}
210222

211-
items.Remove(best);
212-
ordered.Enqueue(best);
223+
if (bestIndex != start)
224+
{
225+
(items[start], items[bestIndex]) = (items[bestIndex], items[start]);
226+
}
213227
}
214228

215-
return ordered.ToArray();
229+
return items;
216230
}
217231

218232
/// <summary>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Linq;
5+
using NuGet.Protocol.Core.Types;
6+
using Xunit;
7+
8+
namespace NuGet.Protocol.Tests;
9+
10+
public class SourceRepositoryTests
11+
{
12+
[Fact]
13+
public void ProviderCacheTypes_EqualsV3ResourceCount()
14+
{
15+
Assert.Equal(SourceRepository.ProviderCacheTypes, Repository.Provider.GetCoreV3().GroupBy(p => p.Value.ResourceType).Count());
16+
}
17+
}

0 commit comments

Comments
 (0)