Skip to content

Commit c830aad

Browse files
committed
Source Generator Performance Improvements:
- Reversed lookup order (Extension second) - Introduced type cache ``` PERF PROGRESS: SourceGen - Maui.Controls.Sample (262 XAML files) 1) Unchanged: - 15640 GetTypeByMetadata calls - 223s 2) Extensions lookup in XmlTypeXamlExtensions.GetTypeReference() second - 6232 GetTypeByMetadata calls (~60% reduction) - 90s (~60% reduction) 3) With type cache - 203 GetTypeByMetadata calls (~97% reduction => ~99% total reduction) - 6s (~93% reduction => ~97% total reduction => 37 times faster!) ```
1 parent d594604 commit c830aad

File tree

5 files changed

+64
-14
lines changed

5 files changed

+64
-14
lines changed

src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
5252
</ItemGroup>
5353

54+
<ItemGroup>
55+
<ProjectReference
56+
Include="..\..\..\Controls\src\Core\Controls.SourceGen.csproj"
57+
OutputItemType="Analyzer"
58+
ReferenceOutputAssembly="false"/>
59+
</ItemGroup>
60+
5461
<ItemGroup>
5562
<ProjectReference Include="..\..\..\BlazorWebView\samples\MauiRazorClassLibrarySample\MauiRazorClassLibrarySample.csproj" />
5663
</ItemGroup>

src/Controls/src/SourceGen/CodeBehindGenerator.cs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.IO;
45
using System.Linq;
56
using System.Reflection;
@@ -44,20 +45,25 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
4445
var xmlnsDefinitionsProvider = initContext.CompilationProvider
4546
.Select(GetAssemblyAttributes);
4647

48+
var typeCacheProvider = initContext.CompilationProvider
49+
.Select(GetTypeCache);
50+
4751
var sourceProvider = projectItemProvider
4852
.Combine(xmlnsDefinitionsProvider)
53+
.Combine(typeCacheProvider)
4954
.Combine(initContext.CompilationProvider)
5055
.Select(static (t, _) => (t.Left.Left, t.Left.Right, t.Right));
5156

5257
initContext.RegisterSourceOutput(sourceProvider, static (sourceProductionContext, provider) =>
5358
{
54-
var (projectItem, caches, compilation) = provider;
59+
var ((projectItem, xmlnsCache), typeCache, compilation) = provider;
5560
if (projectItem == null)
5661
return;
62+
5763
switch (projectItem.Kind)
5864
{
5965
case "Xaml":
60-
GenerateXamlCodeBehind(projectItem, compilation, sourceProductionContext, caches);
66+
GenerateXamlCodeBehind(projectItem, compilation, sourceProductionContext, xmlnsCache, typeCache);
6167
break;
6268
case "Css":
6369
GenerateCssCodeBehind(projectItem, sourceProductionContext);
@@ -137,7 +143,12 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio
137143
return new AssemblyCaches(xmlnsDefinitions, internalsVisible);
138144
}
139145

140-
static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation, SourceProductionContext context, AssemblyCaches caches)
146+
static IDictionary<XmlType, string> GetTypeCache(Compilation compilation, CancellationToken cancellationToken)
147+
{
148+
return new Dictionary<XmlType, string>();
149+
}
150+
151+
static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation, SourceProductionContext context, AssemblyCaches caches, IDictionary<XmlType, string> typeCache)
141152
{
142153
var text = projItem.AdditionalText.GetText(context.CancellationToken);
143154
if (text == null)
@@ -149,7 +160,7 @@ static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation
149160
return;
150161
var uid = Crc64.ComputeHashString($"{compilation.AssemblyName}.{itemName}");
151162

152-
if (!TryParseXaml(text, uid, compilation, caches, context.CancellationToken, projItem.TargetFramework, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields, out var parseException))
163+
if (!TryParseXaml(text, uid, compilation, caches, typeCache, context.CancellationToken, projItem.TargetFramework, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields, out var parseException))
153164
{
154165
if (parseException != null)
155166
{
@@ -237,7 +248,7 @@ static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation
237248
context.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8));
238249
}
239250

240-
static bool TryParseXaml(SourceText text, string uid, Compilation compilation, AssemblyCaches caches, CancellationToken cancellationToken, string? targetFramework, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string, string)>? namedFields, out Exception? exception)
251+
static bool TryParseXaml(SourceText text, string uid, Compilation compilation, AssemblyCaches caches, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken, string? targetFramework, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string, string)>? namedFields, out Exception? exception)
241252
{
242253
cancellationToken.ThrowIfCancellationRequested();
243254

@@ -320,9 +331,9 @@ static bool TryParseXaml(SourceText text, string uid, Compilation compilation, A
320331
return true;
321332
}
322333

323-
namedFields = GetNamedFields(root, nsmgr, compilation, caches, cancellationToken);
334+
namedFields = GetNamedFields(root, nsmgr, compilation, caches, typeCache, cancellationToken);
324335
var typeArguments = GetAttributeValue(root, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
325-
baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, caches);
336+
baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, caches, typeCache);
326337

327338
// x:ClassModifier attribute
328339
var classModifier = GetAttributeValue(root, "ClassModifier", XamlParser.X2006Uri, XamlParser.X2009Uri);
@@ -345,7 +356,7 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
345356
return true;
346357
}
347358

348-
static IEnumerable<(string name, string type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches caches, CancellationToken cancellationToken)
359+
static IEnumerable<(string name, string type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches caches, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken)
349360
{
350361
var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri);
351362
if (xPrefix == null)
@@ -371,13 +382,17 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
371382
var accessModifier = fieldModifier?.ToLowerInvariant().Replace("notpublic", "internal") ?? "private"; //notpublic is WPF for internal
372383
if (!new[] { "private", "public", "internal", "protected" }.Contains(accessModifier)) //quick validation
373384
accessModifier = "private";
374-
yield return (name ?? "", GetTypeName(xmlType, compilation, caches), accessModifier);
385+
yield return (name ?? "", GetTypeName(xmlType, compilation, caches, typeCache), accessModifier);
375386
}
376387
}
377388

378-
static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches caches)
389+
static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches caches, IDictionary<XmlType, string> typeCache)
379390
{
380-
string returnType;
391+
if (typeCache.TryGetValue(xmlType, out string returnType))
392+
{
393+
return returnType;
394+
}
395+
381396
var ns = GetClrNamespace(xmlType.NamespaceUri);
382397
if (ns != null)
383398
returnType = $"{ns}.{xmlType.Name}";
@@ -388,9 +403,11 @@ static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCach
388403
}
389404

390405
if (xmlType.TypeArguments != null)
391-
returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, caches)))}>";
406+
returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, caches, typeCache)))}>";
392407

393-
return $"global::{returnType}";
408+
returnType = $"global::{returnType}";
409+
typeCache[xmlType] = returnType;
410+
return returnType;
394411
}
395412

396413
static string? GetClrNamespace(string namespaceuri)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"profiles": {
3+
"SourceGen - Maui.Controls.Sample": {
4+
"commandName": "DebugRoslynComponent",
5+
"targetProject": "..\\..\\samples\\Controls.Sample\\Maui.Controls.Sample.csproj"
6+
}
7+
}
8+
}

src/Controls/src/Xaml/XamlNode.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ public XmlType(string namespaceUri, string name, IList<XmlType> typeArguments)
5353
public string NamespaceUri { get; }
5454
public string Name { get; }
5555
public IList<XmlType> TypeArguments { get; }
56+
57+
public override bool Equals(object obj)
58+
{
59+
if (obj is not XmlType other)
60+
{
61+
return false;
62+
}
63+
64+
return
65+
NamespaceUri == other.NamespaceUri &&
66+
Name == other.Name &&
67+
(TypeArguments == null && other.TypeArguments == null || TypeArguments.SequenceEqual(other.TypeArguments));
68+
}
69+
70+
public override int GetHashCode()
71+
{
72+
return NamespaceUri.GetHashCode() ^ Name.GetHashCode();
73+
}
5674
}
5775

5876
abstract class BaseNode : IXmlLineInfo, INode

src/Controls/src/Xaml/XmlTypeXamlExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ static class XmlTypeXamlExtensions
6161
}
6262

6363
var lookupNames = new List<string>();
64+
lookupNames.Add(elementName);
6465
if (elementName != "DataTemplate" && !elementName.EndsWith("Extension", StringComparison.Ordinal))
6566
lookupNames.Add(elementName + "Extension");
66-
lookupNames.Add(elementName);
6767

6868
for (var i = 0; i < lookupNames.Count; i++)
6969
{

0 commit comments

Comments
 (0)