Skip to content

Commit cfe3b7c

Browse files
jonathanpeppersjonpryor
authored andcommitted
[linker] remove unneeded string.Format() calls (#4260)
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.
1 parent aa8eba4 commit cfe3b7c

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
### Linker Behavior Change
2+
3+
Xamarin.Android has to consolidate changes to the Android APIs and how
4+
they affect C# types. If you consider an example such as:
5+
6+
* API 28 has an `IFoo` interface in `Mono.Android.dll` for `v9.0`.
7+
* C# code implements `IFoo` in class `Bar`.
8+
* API 29 adds another method to `IFoo` in `Mono.Android.dll` for
9+
`v10.0`.
10+
* Apps built targeting API 30 / `v10.0` will fix up `Bar` to implement
11+
the new method, but throw `Java.Lang.AbstractMethodError` if it was
12+
called.
13+
14+
If the linker didn't make this change, you would instead hit a crash
15+
when trying to create an instance of type `Bar` such as:
16+
17+
System.TypeLoadException: VTable setup of type 'Bar' failed.
18+
19+
Xamarin.Android has some performance improvements to the linker in
20+
this area. Existing apps *should* not be affected.
21+
22+
Submit a [bug report][bug] if you find an issue in this area.
23+
24+
[bug]: https://github.com/xamarin/xamarin-android/wiki/Submitting-Bugs,-Feature-Requests,-and-Pull-Requests

src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ bool HaveSameSignature (TypeReference iface, MethodDefinition iMethod, MethodDef
158158
if (IsInOverrides (iMethod, tMethod))
159159
return true;
160160

161-
if (iMethod.Name != tMethod.Name && (iMethod.DeclaringType == null || (iMethod.DeclaringType.DeclaringType == null ? (string.Format ("{0}.{1}", iface.FullName, iMethod.Name) != tMethod.Name) : (string.Format ("{0}.{1}.{2}", iMethod.DeclaringType.DeclaringType, iface.Name, iMethod.Name) != tMethod.Name))))
161+
if (iMethod.Name != tMethod.Name)
162162
return false;
163163

164164
if (!CompareTypes (iMethod.MethodReturnType.ReturnType, tMethod.MethodReturnType.ReturnType))

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,69 @@ static void CreateAbstractIfaceImplementation (string assemblyPath, AssemblyDefi
7070
}
7171
}
7272

73+
[Test]
74+
public void FixAbstractMethodsStep_Explicit ()
75+
{
76+
var path = Path.Combine (Path.GetFullPath (XABuildPaths.TestOutputDirectory), "temp", TestName);
77+
var step = new FixAbstractMethodsStep ();
78+
var pipeline = new Pipeline ();
79+
80+
Directory.CreateDirectory (path);
81+
82+
using (var context = new LinkContext (pipeline)) {
83+
84+
context.Resolver.AddSearchDirectory (path);
85+
86+
var myAssemblyPath = Path.Combine (path, "MyAssembly.dll");
87+
88+
using (var android = CreateFauxMonoAndroidAssembly ()) {
89+
android.Write (Path.Combine (path, "Mono.Android.dll"));
90+
CreateExplicitInterface (myAssemblyPath, android);
91+
}
92+
93+
using (var assm = context.Resolve (myAssemblyPath)) {
94+
step.Process (context);
95+
96+
var impl = assm.MainModule.GetType ("MyNamespace.MyClass");
97+
Assert.AreEqual (2, impl.Methods.Count, "MyClass should contain 2 methods");
98+
var method = impl.Methods.FirstOrDefault (m => m.Name == "MyNamespace.IMyInterface.MyMethod");
99+
Assert.IsNotNull (method, "MyNamespace.IMyInterface.MyMethod should exist");
100+
method = impl.Methods.FirstOrDefault (m => m.Name == "MyMissingMethod");
101+
Assert.IsNotNull (method, "MyMissingMethod should exist");
102+
}
103+
}
104+
105+
Directory.Delete (path, true);
106+
}
107+
108+
static void CreateExplicitInterface (string assemblyPath, AssemblyDefinition android)
109+
{
110+
using (var assm = AssemblyDefinition.CreateAssembly (new AssemblyNameDefinition ("NestedIFaceTest", new Version ()), "NestedIFaceTest", ModuleKind.Dll)) {
111+
var void_type = assm.MainModule.ImportReference (typeof (void));
112+
113+
// Create interface
114+
var iface = new TypeDefinition ("MyNamespace", "IMyInterface", TypeAttributes.Interface);
115+
116+
var iface_method = new MethodDefinition ("MyMethod", MethodAttributes.Abstract, void_type);
117+
iface.Methods.Add (iface_method);
118+
iface.Methods.Add (new MethodDefinition ("MyMissingMethod", MethodAttributes.Abstract, void_type));
119+
120+
assm.MainModule.Types.Add (iface);
121+
122+
// Create implementing class
123+
var jlo = assm.MainModule.Import (android.MainModule.GetType ("Java.Lang.Object"));
124+
var impl = new TypeDefinition ("MyNamespace", "MyClass", TypeAttributes.Public, jlo);
125+
impl.Interfaces.Add (new InterfaceImplementation (iface));
126+
127+
var explicit_method = new MethodDefinition ("MyNamespace.IMyInterface.MyMethod", MethodAttributes.Abstract, void_type);
128+
explicit_method.Overrides.Add (new MethodReference (iface_method.Name, void_type, iface));
129+
impl.Methods.Add (explicit_method);
130+
131+
assm.MainModule.Types.Add (impl);
132+
assm.Write (assemblyPath);
133+
}
134+
}
135+
73136
static AssemblyDefinition CreateFauxMonoAndroidAssembly ()
74137
{
75138
var assm = AssemblyDefinition.CreateAssembly (new AssemblyNameDefinition ("Mono.Android", new Version ()), "DimTest", ModuleKind.Dll);

0 commit comments

Comments
 (0)