Skip to content

[linker] remove unneeded string.Format calls - #4260

Merged
jonpryor merged 1 commit into
dotnet:masterfrom
jonathanpeppers:fixabstractmethods-strings
Feb 20, 2020
Merged

[linker] remove unneeded string.Format calls#4260
jonpryor merged 1 commit into
dotnet:masterfrom
jonathanpeppers:fixabstractmethods-strings

Conversation

@jonathanpeppers

@jonathanpeppersjonathanpeppers commented Feb 12, 2020

Copy link
Copy Markdown
Member

I was using the Mono profiler for our build, and I noticed:

Allocation summary
Bytes Count Average Type name
180765632 2142803 84 System.String
53218472 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
(wrapper managed-to-native) string:FastAllocateString (int)

~53MB of string from this one method is a lot!

I found that one of these string.Format calls are running for almost
every method on every interface in every assembly:

(string.Format ("{0}.{1}", iface.FullName, iMethod.Name) != tMethod.Name) :
(string.Format ("{0}.{1}.{2}", iMethod.DeclaringType.DeclaringType, iface.Name, iMethod.Name) != tMethod.Name))

Even the simplest non-matching cases would call string.Format:

iMethod.Name == "Foo"
tMethod.Name == "Bar"

In all of these cases...

  • Class implements interface implicitly
  • Class implements interface explicitly
  • Class implements abstract class

We found that the IsInOverrides check should work or merely compare
iMethod.Name and tMethod.Name. We don't seem to need the
string.Format calls at all?

Results

An initial build of the Xamarin.Forms integration project on
Windows/.NET framework:

Before:
796 ms LinkAssembliesNoShrink 1 calls
After:
767 ms LinkAssembliesNoShrink 1 calls

The same project on macOS/Mono (slightly slower machine, too):

Before:
1341 ms LinkAssembliesNoShrink 1 calls
After:
1025 ms LinkAssembliesNoShrink 1 calls

String allocations are way better, too:

Before:
Allocation summary
Bytes Count Average Type name
180765632 2142803 84 System.String
After:
Allocation summary
Bytes Count Average Type name
127736832 1652070 77 System.String

Shows 53,028,800 bytes saved.

Before:
53218472 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
After:
1933624 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameMethodName (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)

Shows 51,284,848 bytes saved.

I don't know about the discrepancy between these two sets of numbers.
But I think this is roughly ~50MB less string allocations.

@jonathanpeppers
jonathanpeppersforce-pushed the fixabstractmethods-strings branch from 43e1cdc to c5fc434CompareFebruary 13, 2020 20:59
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jonpryor

jonpryor commented Feb 14, 2020

Copy link
Copy Markdown
Contributor

The short circuit and reduced memory usage is nice and all but...are we still doing too much?

The point behind PR #861 and the above IsInOverrides() invocation was to see if tMethod was supposed to override the interface method iMethod.

If that fails, then we fallback to...a whole mess of string work.

Why do we need to do that whole mess of string work at all? Why do we needHaveSameMethodName()? Why do we need to compare parameter types & return types?

Why isn't IsInOverrides()by itself "enough"? What test case am I not thinking about?

(With an added point that method names being {iface.FullName}.{iMethod.Name} or {declaringType}.{iface.Name}.{iMethod.Name} is really only true for C# code, and IIRC isn't true for VB.NET code or F# code, so if we can avoid string comparisons entirely that would be Really Nice...)

@radekdoulik?

@jonpryor

Copy link
Copy Markdown
Contributor

More on the "F# names don't match the C# convention", consider this F# code which implements the IServiceProvider.GetService(Type) method:

openSystem;
typeMyServiceProvider()=interface IServiceProvider withmemberthis.GetService(serviceType :Type):System.Object =null

When using "Microsoft (R) F# Compiler version 10.2.3 for F# 4.5", the monodic output shows:

 .class nested public auto ansi serializable MyServiceProvider
extends [mscorlib]System.Object
implements [mscorlib]System.IServiceProvider {
.custom instance void class [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::'.ctor'(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = (01 00 03 00 00 00 00 00 ) // ........
// method line 2
.method private virtual hidebysig newslot instance default object 'System-IServiceProvider-GetService' (class [mscorlib]System.Type serviceType) cil managed {
// Method begins at RVA 0x205c
.override class [mscorlib]System.IServiceProvider::GetService
// Code size 2 (0x2)
.maxstack 8
IL_0000: ldnull IL_0001: ret } // end of method MyServiceProvider::System-IServiceProvider-GetService
} // end of class MyServiceProvider

That is, the method name which implements IServiceProvider.GetService is named System-IServiceProvider-GetService, which uses hyphens (dashes? minus signs?) within the name, which is something we are not checking for.

Nor should we! The IL explicitly states which interface method is implemented!

	.override class [mscorlib]System.IServiceProvider::GetService

@radekdoulik

radekdoulik commented Feb 17, 2020

Copy link
Copy Markdown
Member

Why isn't IsInOverrides() by itself "enough"?

IIRC, the .override is not always present in IL.

@jonathanpeppers do you have your new test case IL around? I think that might show that as the test case is going through the string comparison branch, right?

@jonpryor

Copy link
Copy Markdown
Contributor

IIRC, the .override is not always present in IL.

...and this is the point where I start burbling like a looney.

In fact, virtual methods overrides do not use .override!

classApp{publicoverridestringToString(){return"Hello, App!";}}

disassembles (for csc /optimize+ output) to:

 // method line 2
.method public virtual hidebysig instance default string ToString () cil managed {
// Method begins at RVA 0x2061
// Code size 6 (0x6)
.maxstack 8
IL_0000: ldstr "Hello, App!"
IL_0005: ret } // end of method App::ToString

which in no way specifies what method it's overriding.

This is why we need to fallback to signature-based matching: for virtual methods that are overridden in derived types. Interface methods get the "easy" path of .override.

😭


static void CreateNestedInterface (string assemblyPath, AssemblyDefinition android)
{
using (var assm = AssemblyDefinition.CreateAssembly (new AssemblyNameDefinition ("NestedIFaceTest", new Version ()), "NestedIFaceTest", ModuleKind.Dll)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Possibly "silly" question, but why are we using System.Reflection.Emit to create MyAssembly.dll instead of using some C# code?

(Yes, FixAbstractMethodsStep_NestedTypes() does that too, but now I wonder the same thing as well!)

@jonathanpeppersjonathanpeppersFeb 20, 2020

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I tried using CodeDom... It seems like sacrificing too many chickens, it required me adding global alias for System.dll:

jonathanpeppers@b259e62

ILMerge puts CodeDom types in Xamarin.Android.Build.Tasks.dll:

https://github.com/xamarin/xamarin-android/blob/19cd98b0f0f7709c9383185c64c5500ab9cae00f/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets#L308

I didn't get to the point of how to "add" a new method to an interface of an existing assembly. I guess it would overwrite Mono.Android.dll with a new one?

Using only Mono.Cecil it's easy to fabricate the types to make FixAbstractMethods do its work.

@jonpryor

Copy link
Copy Markdown
Contributor

This is why we need to fallback to signature-based matching...

Still, that doesn't quite explain why we need the previous string.Format()-based logic. Sure, virtual methods don't have .override, but virtual methods also have the same name.

The only instances -- that I know of -- where a method name would be $"{iface.FullName}.{iMethod.Name}" or $"{declaringType}.{iface.Name}.{iMethod.Name}" is when the method is part of an explicit interface implementation, in which case it most certainly should have .override!

Which means the "original" question is still unanswered:

Why do we need to do that whole mess of string work at all? Why do we need HaveSameMethodName()?

An actual test case would be useful here. 😕

I was using the Mono profiler for our build, and I noticed:
Allocation summary
Bytes Count Average Type name
180765632 2142803 84 System.String
53218472 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
(wrapper managed-to-native) string:FastAllocateString (int)
~53MB of string from this one method is a lot!
I found that one of these `string.Format` calls are running for almost
every method on every interface in every assembly:
(string.Format ("{0}.{1}", iface.FullName, iMethod.Name) != tMethod.Name) :
(string.Format ("{0}.{1}.{2}", iMethod.DeclaringType.DeclaringType, iface.Name, iMethod.Name) != tMethod.Name))
Even the simplest non-matching cases would call `string.Format`:
iMethod.Name == "Foo"
tMethod.Name == "Bar"
In all of these cases...
* Class implements interface implicitly
* Class implements interface explicitly
* Class implements abstract class
We found that the `IsInOverrides` check should work or merely compare
`iMethod.Name` and `tMethod.Name`. We don't seem to need the
`string.Format` calls at all?
~~ Results ~~
An initial build of the Xamarin.Forms integration project on
Windows/.NET framework:
Before:
796 ms LinkAssembliesNoShrink 1 calls
After:
767 ms LinkAssembliesNoShrink 1 calls
The same project on macOS/Mono (slightly slower machine, too):
Before:
1341 ms LinkAssembliesNoShrink 1 calls
After:
1025 ms LinkAssembliesNoShrink 1 calls
String allocations are way better, too:
Before:
Allocation summary
Bytes Count Average Type name
180765632 2142803 84 System.String
After:
Allocation summary
Bytes Count Average Type name
127736832 1652070 77 System.String
Shows 53,028,800 bytes saved.
Before:
53218472 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
After:
1933624 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameMethodName (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
Shows 51,284,848 bytes saved.
I don't know about the discrepancy between these two sets of numbers.
But I think this is roughly ~50MB less string allocations.
@jonathanpeppers
jonathanpeppersforce-pushed the fixabstractmethods-strings branch from c5fc434 to 83928ffCompareFebruary 20, 2020 17:19
@jonathanpeppersjonathanpeppers changed the title [linker] shortcircuit to prevent string.Format calls[linker] remove unneeded string.Format callsFeb 20, 2020
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

@jonpryor I think I did what we needed here:

  1. Just check the method names and that's it.
  2. Document the release notes.
  3. Make the test case using an explicit interface implementation.

I also experimented with using C# source code for these tests, such as:

https://github.com/xamarin/java.interop/blob/master/tests/generator-Tests/Integration-Tests/Compiler.cs

I encountered a lot of issues trying to make it work (beyond the hacks in java.interop). It might be worth revisiting later I think.

@jonpryor
jonpryor merged commit 113d5b8 into dotnet:masterFeb 20, 2020
@jonathanpeppers
jonathanpeppers deleted the fixabstractmethods-strings branch February 20, 2020 22:42
jonathanpeppers added a commit to jonathanpeppers/java.interop that referenced this pull request Feb 20, 2020
Using the Mono profiler, I found:
Allocation summary
Bytes Count Average Type name
7636736 59662 128 System.Func<Mono.Cecil.TypeDefinition,System.Boolean>
3685952 57593 64 Java.Interop.Tools.Cecil.TypeDefinitionRocks.<GetTypeAndBaseTypes>d__3
The stack traces of these are from:
2498944 bytes from:
Xamarin.Android.Tasks.GenerateJavaStubs:Run (Java.Interop.Tools.Cecil.DirectoryAssemblyResolver)
Xamarin.Android.Tasks.ManifestDocument:Merge (Microsoft.Build.Utilities.TaskLoggingHelper,Java.Interop.Tools.Cecil.TypeDefinitionCache,System.Collections.Generic.List`1<Mono.Cecil.TypeDefinition>,string,bool,string,System.Collections.Generic.IEnumerable`1<string>)
Xamarin.Android.Tasks.ManifestDocument:GetGenerator (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
(wrapper alloc) object:ProfilerAllocSmall (intptr,intptr)
(wrapper managed-to-native) object:__icall_wrapper_mono_profiler_raise_gc_allocation (object)
...
1273344 bytes from:
Xamarin.Android.Tasks.ManifestDocument:Merge (Microsoft.Build.Utilities.TaskLoggingHelper,Java.Interop.Tools.Cecil.TypeDefinitionCache,System.Collections.Generic.List`1<Mono.Cecil.TypeDefinition>,string,bool,string,System.Collections.Generic.IEnumerable`1<string>)
Xamarin.Android.Tasks.ManifestDocument:GetGenerator (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:GetTypeAndBaseTypes (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
(wrapper alloc) object:ProfilerAllocSmall (intptr,intptr)
(wrapper managed-to-native) object:__icall_wrapper_mono_profiler_raise_gc_allocation (object)
This `IsSubclassOf` method gets called ~40K times during a build:
Method call summary
Total(ms) Self(ms) Calls Method name
2431 117 43493 Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Reviewing `IsSubclassOf` we can just remove the System.Linq usage and
use a `foreach` loop instead.
The results of building the Xamarin.Forms integration project on
Windows:
Before:
559 ms GenerateJavaStubs 1 calls
After:
535 ms GenerateJavaStubs 1 calls
A ~24ms savings is pretty good for a small app.
I suspect it would have even better improvements on macOS / Mono, due
to what I saw in: dotnet/android#4260
jonpryor pushed a commit that referenced this pull request Feb 21, 2020
I was using the Mono profiler for our build, and I noticed:
Allocation summary
Bytes Count Average Type name
180765632 2142803 84 System.String
53218472 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
(wrapper managed-to-native) string:FastAllocateString (int)
~53MB of string from this one method is a lot!
I found that one of these `string.Format()` calls are running for
almost every method on every interface in every assembly:
? (string.Format ("{0}.{1}", iface.FullName, iMethod.Name) != tMethod.Name)
: (string.Format ("{0}.{1}.{2}", iMethod.DeclaringType.DeclaringType, iface.Name, iMethod.Name) != tMethod.Name))
Even the simplest non-matching cases would call `string.Format()`:
iMethod.Name == "Foo"
tMethod.Name == "Bar"
In all of these cases...
* Class implements interface implicitly, or
* Class implements interface explicitly, or
* Class implements abstract class
We found that either the `IsInOverrides()` check should work or
comparing `iMethod.Name` and `tMethod.Name` should work. We don't
seem to need the `string.Format()` calls at all?
~~ Results ~~
An initial build of the Xamarin.Forms integration project on
Windows/.NET framework saves ~30ms:
* Before:
796 ms LinkAssembliesNoShrink 1 calls
* After:
767 ms LinkAssembliesNoShrink 1 calls
The same project on macOS/Mono (slightly slower machine, too)
saves ~316ms:
* Before:
1341 ms LinkAssembliesNoShrink 1 calls
* After:
1025 ms LinkAssembliesNoShrink 1 calls
String allocations are way better, too:
* Before:
Allocation summary
Bytes Count Average Type name
180765632 2142803 84 System.String
* After:
Allocation summary
Bytes Count Average Type name
127736832 1652070 77 System.String
Shows 53,028,800 bytes saved.
* Before:
53218472 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
* After:
1933624 bytes from:
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethodsUnconditional (Mono.Cecil.AssemblyDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:FixAbstractMethods (Mono.Cecil.TypeDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameSignature (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
MonoDroid.Tuner.FixAbstractMethodsStep:HaveSameMethodName (Mono.Cecil.TypeReference,Mono.Cecil.MethodDefinition,Mono.Cecil.MethodDefinition)
Shows 51,284,848 bytes saved.
I don't know about the discrepancy between these two sets of numbers.
But I think this is roughly ~50MB less string allocations.
jonpryor pushed a commit to dotnet/java-interop that referenced this pull request Feb 21, 2020
Using the Mono profiler, I found:
Allocation summary
Bytes Count Average Type name
7636736 59662 128 System.Func<Mono.Cecil.TypeDefinition,System.Boolean>
3685952 57593 64 Java.Interop.Tools.Cecil.TypeDefinitionRocks.<GetTypeAndBaseTypes>d__3
The stack traces of these are from:
2498944 bytes from:
Xamarin.Android.Tasks.GenerateJavaStubs:Run (Java.Interop.Tools.Cecil.DirectoryAssemblyResolver)
Xamarin.Android.Tasks.ManifestDocument:Merge (Microsoft.Build.Utilities.TaskLoggingHelper,Java.Interop.Tools.Cecil.TypeDefinitionCache,System.Collections.Generic.List`1<Mono.Cecil.TypeDefinition>,string,bool,string,System.Collections.Generic.IEnumerable`1<string>)
Xamarin.Android.Tasks.ManifestDocument:GetGenerator (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
(wrapper alloc) object:ProfilerAllocSmall (intptr,intptr)
(wrapper managed-to-native) object:__icall_wrapper_mono_profiler_raise_gc_allocation (object)
...
1273344 bytes from:
Xamarin.Android.Tasks.ManifestDocument:Merge (Microsoft.Build.Utilities.TaskLoggingHelper,Java.Interop.Tools.Cecil.TypeDefinitionCache,System.Collections.Generic.List`1<Mono.Cecil.TypeDefinition>,string,bool,string,System.Collections.Generic.IEnumerable`1<string>)
Xamarin.Android.Tasks.ManifestDocument:GetGenerator (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:GetTypeAndBaseTypes (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
(wrapper alloc) object:ProfilerAllocSmall (intptr,intptr)
(wrapper managed-to-native) object:__icall_wrapper_mono_profiler_raise_gc_allocation (object)
This `TypeDefinitionRocks.IsSubclassOf()` method gets called ~40K
times during a build:
Method call summary
Total(ms) Self(ms) Calls Method name
2431 117 43493 Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Reviewing `IsSubclassOf()` we can just remove the System.Linq usage
and use a `foreach` loop instead.
The results of building the Xamarin.Forms integration project on
Windows:
* Before:
559 ms GenerateJavaStubs 1 calls
* After:
535 ms GenerateJavaStubs 1 calls
A ~24ms savings is pretty good for a small app.
I suspect it would have even better improvements on macOS / Mono, due
to what I saw in: dotnet/android#4260
jonpryor pushed a commit to dotnet/java-interop that referenced this pull request Feb 24, 2020
Using the Mono profiler, I found:
Allocation summary
Bytes Count Average Type name
7636736 59662 128 System.Func<Mono.Cecil.TypeDefinition,System.Boolean>
3685952 57593 64 Java.Interop.Tools.Cecil.TypeDefinitionRocks.<GetTypeAndBaseTypes>d__3
The stack traces of these are from:
2498944 bytes from:
Xamarin.Android.Tasks.GenerateJavaStubs:Run (Java.Interop.Tools.Cecil.DirectoryAssemblyResolver)
Xamarin.Android.Tasks.ManifestDocument:Merge (Microsoft.Build.Utilities.TaskLoggingHelper,Java.Interop.Tools.Cecil.TypeDefinitionCache,System.Collections.Generic.List`1<Mono.Cecil.TypeDefinition>,string,bool,string,System.Collections.Generic.IEnumerable`1<string>)
Xamarin.Android.Tasks.ManifestDocument:GetGenerator (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
(wrapper alloc) object:ProfilerAllocSmall (intptr,intptr)
(wrapper managed-to-native) object:__icall_wrapper_mono_profiler_raise_gc_allocation (object)
...
1273344 bytes from:
Xamarin.Android.Tasks.ManifestDocument:Merge (Microsoft.Build.Utilities.TaskLoggingHelper,Java.Interop.Tools.Cecil.TypeDefinitionCache,System.Collections.Generic.List`1<Mono.Cecil.TypeDefinition>,string,bool,string,System.Collections.Generic.IEnumerable`1<string>)
Xamarin.Android.Tasks.ManifestDocument:GetGenerator (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Java.Interop.Tools.Cecil.TypeDefinitionRocks:GetTypeAndBaseTypes (Mono.Cecil.TypeDefinition,Java.Interop.Tools.Cecil.TypeDefinitionCache)
(wrapper alloc) object:ProfilerAllocSmall (intptr,intptr)
(wrapper managed-to-native) object:__icall_wrapper_mono_profiler_raise_gc_allocation (object)
This `TypeDefinitionRocks.IsSubclassOf()` method gets called ~40K
times during a build:
Method call summary
Total(ms) Self(ms) Calls Method name
2431 117 43493 Java.Interop.Tools.Cecil.TypeDefinitionRocks:IsSubclassOf (Mono.Cecil.TypeDefinition,string,Java.Interop.Tools.Cecil.TypeDefinitionCache)
Reviewing `IsSubclassOf()` we can just remove the System.Linq usage
and use a `foreach` loop instead.
The results of building the Xamarin.Forms integration project on
Windows:
* Before:
559 ms GenerateJavaStubs 1 calls
* After:
535 ms GenerateJavaStubs 1 calls
A ~24ms savings is pretty good for a small app.
I suspect it would have even better improvements on macOS / Mono, due
to what I saw in: dotnet/android#4260
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 28, 2024
Sign up for freeto 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.

4 participants

@jonathanpeppers@jonpryor@radekdoulik@brendanzagaeski