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

[Xamarin.Android.Build.Tasks] reduce System.Linq usage in ManifestDocument #9281

Merged
merged 1 commit into from
Sep 4, 2024

Conversation

jonathanpeppers
Copy link
Member

Profiling an incremental build of a dotnet new maui project with a XAML change, one thing I saw was time spent in <GenerateJavaStubs/> MSBuild task, and the ManifestDocument class:

67.57ms (1.80%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.Merge(class Microsoft.Build.Utilities.TaskLoggingHel
19.99ms (0.53%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.CreateApplicationElement(class System.Xml.Linq.XEleme...

Reviewing the code, there was a decent amount of LINQ usage that would create extra allocations for closures, such as:

var properties =
    Assemblies.SelectMany (path => PropertyAttribute.FromCustomAttributeProvider (Resolver.GetAssembly (path), cache))

I unrolled the System.Linq usage, and just used plain foreach loops instead.

I also found some places that did:

if (!attrs.Any ())
    yield break;

We could just remove these as the yield return wouldn't return if attrs is empty anyway.

After removing this code, I see a small improvement:

62.56ms (1.80%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.Merge(class Microsoft.Build.Utilities.TaskLoggingHe...
16.03ms (0.46%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.CreateApplicationElement(class System.Xml.Linq.XEleme...

This probably saves ~5ms to incremental builds, but seems like a straightforward change.

…ument

Profiling an incremental build of a `dotnet new maui` project with a
XAML change, one thing I saw was time spent in `<GenerateJavaStubs/>`
MSBuild task, and the `ManifestDocument` class:

    67.57ms (1.80%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.Merge(class Microsoft.Build.Utilities.TaskLoggingHel
    19.99ms (0.53%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.CreateApplicationElement(class System.Xml.Linq.XEleme...

Reviewing the code, there was a decent amount of LINQ usage that would
create extra allocations for closures, such as:

    var properties =
        Assemblies.SelectMany (path => PropertyAttribute.FromCustomAttributeProvider (Resolver.GetAssembly (path), cache))

I unrolled the System.Linq usage, and just used plain `foreach` loops
instead.

I also found some places that did:

    if (!attrs.Any ())
        yield break;

We could just remove these as the `yield return` wouldn't return if
`attrs` is empty *anyway*.

After removing this code, I see a small improvement:

    62.56ms (1.80%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.Merge(class Microsoft.Build.Utilities.TaskLoggingHe...
    16.03ms (0.46%) xamarin.android.build.tasks!Xamarin.Android.Tasks.ManifestDocument.CreateApplicationElement(class System.Xml.Linq.XEleme...

This probably saves ~5ms to incremental builds, but seems like a
straightforward change.
Comment on lines -622 to -627
var ull1 = usesLibraryAttr ?? Array.Empty<UsesLibraryAttribute> ();
var ull2 = typeUsesLibraryAttr.AsEnumerable () ?? Array.Empty<UsesLibraryAttribute> ();
var usesLibraryAttrs = ull1.Concat (ull2);
var ucl1 = usesConfigurationAttr ?? Array.Empty<UsesConfigurationAttribute>();
var ucl2 = typeUsesConfigurationAttr.AsEnumerable () ?? Array.Empty<UsesConfigurationAttribute> ();
var usesConfigurationattrs = ucl1.Concat (ucl2);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this made lots of locals before, and we didn't really need them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup looks like this code above was just to concatinate the lists. But you already do that in the new foreach loop replacement.

Comment on lines -652 to +663
AddUsesLibraries (application, usesLibraryAttrs, cache);
AddUsesConfigurations (application, usesConfigurationattrs, cache);
AddUsesLibraries (application, usesLibraryAttr, cache);
AddUsesConfigurations (application, usesConfigurationAttr, cache);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just used the original list instead.

Comment on lines -889 to +910
void AddUsesConfigurations (XElement application, IEnumerable<UsesConfigurationAttribute> configs, TypeDefinitionCache cache)
void AddUsesConfigurations (XElement application, List<UsesConfigurationAttribute> configs, TypeDefinitionCache cache)
{
foreach (var uca in configs)
application.Add (uca.ToElement (PackageName, cache));
}

void AddUsesLibraries (XElement application, IEnumerable<UsesLibraryAttribute> libraries, TypeDefinitionCache cache)
void AddUsesLibraries (XElement application, List<UsesLibraryAttribute> libraries, TypeDefinitionCache cache)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These might allow the C# compiler to automatically turn the foreach into for, if the List<T> type is known.

@jonathanpeppers jonathanpeppers merged commit aeeb51a into dotnet:main Sep 4, 2024
55 of 57 checks passed
@jonathanpeppers jonathanpeppers deleted the ManifestDocumentLinq branch September 4, 2024 13:40
@github-actions github-actions bot locked and limited conversation to collaborators Oct 5, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants