Skip to content

Commit 54451be

Browse files
#136 Insufficient performance
1 parent 5205752 commit 54451be

4 files changed

Lines changed: 130 additions & 60 deletions

File tree

src/Pure.DI.Core/Core/ApiInvocationProcessor.cs

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ sealed class ApiInvocationProcessor(
2222
INameProvider nameProvider)
2323
: IApiInvocationProcessor
2424
{
25-
private static readonly char[] TypeNamePartsSeparators = ['.'];
26-
2725
public void ProcessInvocation(
2826
IMetadataVisitor metadataVisitor,
2927
SemanticModel semanticModel,
@@ -1138,17 +1136,20 @@ private void VisitFactory(
11381136
var factoryApiWalker = factoryApiWalkerFactory();
11391137
factoryApiWalker.Initialize(semanticModel, contextParameter, lambdaExpression);
11401138
factoryApiWalker.Visit(lambdaExpression);
1139+
var contextSymbol = contextParameter.SyntaxTree == semanticModel.SyntaxTree
1140+
? semanticModel.GetDeclaredSymbol(contextParameter)
1141+
: null;
11411142
var resolversHasContextTag = false;
11421143
var resolvers = factoryApiWalker.Meta
11431144
.Where(i => i.Kind == FactoryMetaKind.Resolver)
1144-
.Select(meta => CreateResolver(semanticModel, resultType, meta, contextParameter, ref resolversHasContextTag, localVariableRenamingRewriter))
1145+
.Select(meta => CreateResolver(semanticModel, resultType, meta, contextParameter, contextSymbol, ref resolversHasContextTag, localVariableRenamingRewriter))
11451146
.Where(i => i != default)
11461147
.ToImmutableArray();
11471148

11481149
var initializersHasContextTag = false;
11491150
var initializers = factoryApiWalker.Meta
11501151
.Where(i => i.Kind == FactoryMetaKind.Initializer)
1151-
.Select(meta => CreateInitializer(semanticModel, resultType, meta, contextParameter, ref initializersHasContextTag, localVariableRenamingRewriter))
1152+
.Select(meta => CreateInitializer(semanticModel, resultType, meta, contextParameter, contextSymbol, ref initializersHasContextTag, localVariableRenamingRewriter))
11521153
.Where(i => i != default)
11531154
.ToImmutableArray();
11541155

@@ -1170,12 +1171,13 @@ private MdOverride CreateOverride(
11701171
SemanticModel semanticModel,
11711172
OverrideMeta @override,
11721173
ParameterSyntax contextParameter,
1174+
ISymbol? contextSymbol,
11731175
ILocalVariableRenamingRewriter localVariableRenamingRewriter,
11741176
out bool hasContextTag)
11751177
{
11761178
hasContextTag = false;
11771179
var invocation = @override.Expression;
1178-
if (!IsContextInvocation(semanticModel, invocation, contextParameter))
1180+
if (!IsContextInvocation(semanticModel, invocation, contextParameter, contextSymbol))
11791181
{
11801182
return default;
11811183
}
@@ -1282,11 +1284,12 @@ private MdInitializer CreateInitializer(
12821284
ITypeSymbol resultType,
12831285
FactoryMeta meta,
12841286
ParameterSyntax contextParameter,
1287+
ISymbol? contextSymbol,
12851288
ref bool hasContextTag,
12861289
ILocalVariableRenamingRewriter localVariableRenamingRewriter)
12871290
{
12881291
var invocation = meta.Expression;
1289-
if (!IsContextInvocation(semanticModel, invocation, contextParameter))
1292+
if (!IsContextInvocation(semanticModel, invocation, contextParameter, contextSymbol))
12901293
{
12911294
return default;
12921295
}
@@ -1301,7 +1304,7 @@ private MdInitializer CreateInitializer(
13011304
// ReSharper disable once LoopCanBeConvertedToQuery
13021305
foreach (var @override in meta.Overrides)
13031306
{
1304-
var mdOverride = CreateOverride(semanticModel, @override, contextParameter, localVariableRenamingRewriter, out hasContextTag);
1307+
var mdOverride = CreateOverride(semanticModel, @override, contextParameter, contextSymbol, localVariableRenamingRewriter, out hasContextTag);
13051308
if (mdOverride != default)
13061309
{
13071310
overrides.Add(mdOverride);
@@ -1322,11 +1325,12 @@ private MdResolver CreateResolver(
13221325
ITypeSymbol resultType,
13231326
FactoryMeta meta,
13241327
ParameterSyntax contextParameter,
1328+
ISymbol? contextSymbol,
13251329
ref bool hasContextTag,
13261330
ILocalVariableRenamingRewriter localVariableRenamingRewriter)
13271331
{
13281332
var invocation = meta.Expression;
1329-
if (!IsContextInvocation(semanticModel, invocation, contextParameter))
1333+
if (!IsContextInvocation(semanticModel, invocation, contextParameter, contextSymbol))
13301334
{
13311335
return default;
13321336
}
@@ -1340,7 +1344,7 @@ private MdResolver CreateResolver(
13401344
// ReSharper disable once LoopCanBeConvertedToQuery
13411345
foreach (var overrideInvocation in meta.Overrides)
13421346
{
1343-
var mdOverride = CreateOverride(semanticModel, overrideInvocation, contextParameter, localVariableRenamingRewriter, out hasContextTag);
1347+
var mdOverride = CreateOverride(semanticModel, overrideInvocation, contextParameter, contextSymbol, localVariableRenamingRewriter, out hasContextTag);
13441348
if (mdOverride != default)
13451349
{
13461350
overrides.Add(mdOverride);
@@ -1407,7 +1411,8 @@ private MdResolver CreateResolver(
14071411
private static bool IsContextInvocation(
14081412
SemanticModel semanticModel,
14091413
InvocationExpressionSyntax invocation,
1410-
ParameterSyntax contextParameter)
1414+
ParameterSyntax contextParameter,
1415+
ISymbol? contextSymbol)
14111416
{
14121417
if (invocation.Expression is not MemberAccessExpressionSyntax
14131418
{
@@ -1432,7 +1437,6 @@ private static bool IsContextInvocation(
14321437
return false;
14331438
}
14341439

1435-
var contextSymbol = semanticModel.GetDeclaredSymbol(contextParameter);
14361440
if (contextSymbol is null)
14371441
{
14381442
return false;
@@ -1521,23 +1525,44 @@ private void NotSupported(SyntaxNode source) =>
15211525

15221526
private IReadOnlyList<T> BuildConstantArgs<T>(
15231527
SemanticModel semanticModel,
1524-
SeparatedSyntaxList<ArgumentSyntax> args) =>
1525-
args
1526-
.SelectMany(a => semantic.GetConstantValues<T>(semanticModel, a.Expression).Select(value => (value, a.Expression)))
1527-
.Select(a => a.value ?? throw new CompileErrorException(
1528-
string.Format(Strings.Error_Template_MustBeValueOfType, a.Expression, typeof(T)),
1529-
ImmutableArray.Create(locationProvider.GetLocation(a.Expression)),
1530-
LogId.ErrorMustBeValueOfType,
1531-
nameof(Strings.Error_Template_MustBeValueOfType)))
1532-
.ToList();
1528+
SeparatedSyntaxList<ArgumentSyntax> args)
1529+
{
1530+
var values = new List<T>(args.Count);
1531+
foreach (var arg in args)
1532+
{
1533+
foreach (var value in semantic.GetConstantValues<T>(semanticModel, arg.Expression))
1534+
{
1535+
if (value is null)
1536+
{
1537+
throw new CompileErrorException(
1538+
string.Format(Strings.Error_Template_MustBeValueOfType, arg.Expression, typeof(T)),
1539+
ImmutableArray.Create(locationProvider.GetLocation(arg.Expression)),
1540+
LogId.ErrorMustBeValueOfType,
1541+
nameof(Strings.Error_Template_MustBeValueOfType));
1542+
}
1543+
1544+
values.Add(value);
1545+
}
1546+
}
1547+
1548+
return values;
1549+
}
15331550

15341551
private ImmutableArray<MdTag> BuildTags(
15351552
SemanticModel semanticModel,
1536-
IEnumerable<ArgumentSyntax> args) =>
1537-
args
1538-
.SelectMany(t => semantic.GetConstantValues<object>(semanticModel, t.Expression, SmartTagKind.Tag))
1539-
.Select((tag, i) => new MdTag(i, tag))
1540-
.ToImmutableArray();
1553+
IEnumerable<ArgumentSyntax> args)
1554+
{
1555+
var tags = new List<MdTag>();
1556+
foreach (var arg in args)
1557+
{
1558+
foreach (var tag in semantic.GetConstantValues<object>(semanticModel, arg.Expression, SmartTagKind.Tag))
1559+
{
1560+
tags.Add(new MdTag(tags.Count, tag));
1561+
}
1562+
}
1563+
1564+
return tags.ToImmutableArray();
1565+
}
15411566

15421567
private static CompositionName CreateCompositionName(
15431568
string name,
@@ -1548,9 +1573,18 @@ private static CompositionName CreateCompositionName(
15481573
string newNamespace;
15491574
if (!string.IsNullOrWhiteSpace(name))
15501575
{
1551-
var compositionTypeNameParts = name.Split(TypeNamePartsSeparators, StringSplitOptions.RemoveEmptyEntries);
1552-
className = compositionTypeNameParts.Last();
1553-
newNamespace = string.Join(".", compositionTypeNameParts.Take(compositionTypeNameParts.Length - 1)).Trim();
1576+
var trimmedName = name.Trim();
1577+
var separatorIndex = trimmedName.LastIndexOf('.');
1578+
if (separatorIndex >= 0)
1579+
{
1580+
className = separatorIndex + 1 < trimmedName.Length ? trimmedName[(separatorIndex + 1)..] : "";
1581+
newNamespace = separatorIndex > 0 ? trimmedName[..separatorIndex] : "";
1582+
}
1583+
else
1584+
{
1585+
className = trimmedName;
1586+
newNamespace = "";
1587+
}
15541588
}
15551589
else
15561590
{

src/Pure.DI.Core/Core/Code/Var.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ record Var(
1616
{
1717
private string? _codeExpression;
1818
private string? _baseName;
19+
private string? _rootBaseName;
1920

2021
/// <summary>
2122
/// Gets the type of the instance.
@@ -51,7 +52,8 @@ public string Name
5152
// because it can change dynamically
5253
if (AbstractNode.ActualLifetime == Lifetime.Singleton && constructors.IsEnabled(graph))
5354
{
54-
return Names.RootFieldName + "." + _baseName;
55+
_rootBaseName ??= Names.RootFieldName + "." + _baseName;
56+
return _rootBaseName;
5557
}
5658

5759
return _baseName;

src/Pure.DI.Core/Core/Code/VarsMap.cs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,24 @@ public VarInjection GetInjection(DependencyGraph graph, in Injection injection,
6565
public IDisposable Root(Lines lines)
6666
{
6767
return Disposables.Create(() => {
68-
_map
69-
.Where(i => !IsRootPersistentNode(i.Value.Declaration.Node))
70-
.ToList()
71-
.ForEach(i => {
68+
var keysToRemove = new List<int>();
69+
foreach (var item in _map)
70+
{
71+
if (IsRootPersistentNode(item.Value.Declaration.Node))
72+
{
73+
continue;
74+
}
75+
7276
#if DEBUG
73-
lines.AppendLine($"// {i.Value.Declaration.Name}: remove ({nameof(Root)})");
77+
lines.AppendLine($"// {item.Value.Declaration.Name}: remove ({nameof(Root)})");
7478
#endif
75-
_map.Remove(i.Key);
76-
});
79+
keysToRemove.Add(item.Key);
80+
}
81+
82+
foreach (var key in keysToRemove)
83+
{
84+
_map.Remove(key);
85+
}
7786

7887
foreach (var var in _map.Values)
7988
{
@@ -93,16 +102,23 @@ public IDisposable LocalFunction(Var var, Lines lines)
93102

94103
// Per-block variables should be isolated between local functions.
95104
var removed = new List<KeyValuePair<int, Var>>();
96-
_map
97-
.Where(i => i.Value.Declaration.Node.ActualLifetime is Lifetime.PerBlock)
98-
.ToList()
99-
.ForEach(i => {
105+
foreach (var item in _map)
106+
{
107+
if (item.Value.Declaration.Node.ActualLifetime is not Lifetime.PerBlock)
108+
{
109+
continue;
110+
}
111+
100112
#if DEBUG
101-
lines.AppendLine($"// {i.Value.Declaration.Name}: remove ({nameof(LocalFunction)})");
113+
lines.AppendLine($"// {item.Value.Declaration.Name}: remove ({nameof(LocalFunction)})");
102114
#endif
103-
removed.Add(i);
104-
_map.Remove(i.Key);
105-
});
115+
removed.Add(item);
116+
}
117+
118+
foreach (var item in removed)
119+
{
120+
_map.Remove(item.Key);
121+
}
106122

107123
return Disposables.Create(() => {
108124
// Cleanup and restore state after exiting the local function.
@@ -168,7 +184,7 @@ node.ActualLifetime is Lifetime.PerBlock
168184
/// Global code state (like LocalFunction) is NOT part of the snapshot because it should accumulate.
169185
/// Only path-specific state (IsDeclared, IsCreated, CodeExpression) is captured.
170186
/// </summary>
171-
private IReadOnlyDictionary<int, VarState> CreateState(Var var)
187+
private Dictionary<int, VarState> CreateState(Var var)
172188
{
173189
var excludeBindingId = var.Declaration.Node.BindingId;
174190
var result = new Dictionary<int, VarState>(_map.Count);
@@ -200,21 +216,31 @@ private void RemoveNewNonPersistentVars(Var var, IReadOnlyDictionary<int, VarSta
200216
#if DEBUG
201217
lines.AppendLine($"// remove new non-persistent vars ({reason} {var.Declaration.Name})");
202218
#endif
203-
var newItems = _map.Where(i => {
204-
if (state.ContainsKey(i.Key))
219+
var newItems = new List<KeyValuePair<int, Var>>();
220+
foreach (var item in _map)
221+
{
222+
if (state.ContainsKey(item.Key))
205223
{
206-
return false;
224+
continue;
207225
}
208226

209-
var node = i.Value.Declaration.Node;
227+
var node = item.Value.Declaration.Node;
210228
if (node.BindingId == var.Declaration.Node.BindingId)
211229
{
212-
return !IsNestedScopePersistentNode(node)
213-
&& !ShouldKeepCurrentNodeInNestedScope(node);
230+
if (!IsNestedScopePersistentNode(node)
231+
&& !ShouldKeepCurrentNodeInNestedScope(node))
232+
{
233+
newItems.Add(item);
234+
}
235+
236+
continue;
214237
}
215238

216-
return !IsNestedScopePersistentNode(node);
217-
}).ToList();
239+
if (!IsNestedScopePersistentNode(node))
240+
{
241+
newItems.Add(item);
242+
}
243+
}
218244

219245
foreach (var item in newItems)
220246
{
@@ -268,10 +294,9 @@ private void RestoreState(Var var, IReadOnlyDictionary<int, VarState> state, Lin
268294

269295
// Find variables that were NOT in the snapshot (newly discovered in the nested scope),
270296
// and reset their path-specific state to defaults.
271-
var stateKeys = new HashSet<int>(state.Keys);
272297
foreach (var item in _map)
273298
{
274-
if (stateKeys.Contains(item.Key))
299+
if (state.ContainsKey(item.Key))
275300
{
276301
continue;
277302
}
@@ -295,7 +320,7 @@ private void RestoreState(Var var, IReadOnlyDictionary<int, VarState> state, Lin
295320
}
296321
}
297322

298-
private record VarState(
323+
private readonly record struct VarState(
299324
Var Var,
300325
bool IsDeclared,
301326
bool IsCreated,

src/Pure.DI.Core/Core/Semantic.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Pure.DI.Core;
44

5+
using System.Collections.Concurrent;
56
using Microsoft.CodeAnalysis.Operations;
67

78
sealed class Semantic(
@@ -12,14 +13,22 @@ sealed class Semantic(
1213
CancellationToken cancellationToken)
1314
: ISemantic
1415
{
16+
private readonly ConcurrentDictionary<TypeSymbolCacheKey, ITypeSymbol?> _typeSymbolCache = new();
17+
1518
public bool IsAccessible(ISymbol symbol) =>
1619
symbol is { IsStatic: false, DeclaredAccessibility: Accessibility.Internal or Accessibility.Public };
1720

1821
public T? TryGetTypeSymbol<T>(SemanticModel semanticModel, SyntaxNode node)
1922
where T : ITypeSymbol
2023
{
21-
var typeInfo = semanticModel.GetTypeInfo(node, cancellationToken);
22-
var typeSymbol = typeInfo.Type ?? typeInfo.ConvertedType;
24+
var typeSymbol = _typeSymbolCache.GetOrAdd(
25+
new TypeSymbolCacheKey(semanticModel, node),
26+
key =>
27+
{
28+
var typeInfo = key.SemanticModel.GetTypeInfo(key.Node, cancellationToken);
29+
return (typeInfo.Type ?? typeInfo.ConvertedType)?.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
30+
});
31+
2332
if (typeSymbol is not T symbol)
2433
{
2534
return default;
@@ -30,7 +39,7 @@ public bool IsAccessible(ISymbol symbol) =>
3039
throw HandledException.Shared;
3140
}
3241

33-
return (T)symbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
42+
return symbol;
3443
}
3544

3645
public T GetTypeSymbol<T>(SemanticModel semanticModel, SyntaxNode node)
@@ -378,5 +387,5 @@ private static bool TryGetValueOf<T>(object? obj, [NotNullWhen(true)] out T? val
378387
public bool IsValidNamespace(INamespaceSymbol? namespaceSymbol) =>
379388
namespaceSymbol is { IsImplicitlyDeclared: false };
380389

381-
internal record NodeKey(SyntaxTree Tree, string Code);
390+
private readonly record struct TypeSymbolCacheKey(SemanticModel SemanticModel, SyntaxNode Node);
382391
}

0 commit comments

Comments
 (0)