Skip to content

Commit c49b934

Browse files
[Xamarin.Android.Build.Tasks] use Java.Interop's TypeDefinitionCache
I was profiling builds with the Mono profiler and noticed: Method call summary Total(ms) Self(ms) Calls Method name 70862 97 89713 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver:Resolve (Mono.Cecil.AssemblyNameReference,Mono.Cecil.ReaderParameters) Almost 90K calls? What is that coming from??? 61422 calls from: Java.Interop.Tools.Cecil.TypeDefinitionRocks/<GetTypeAndBaseTypes>d__1:MoveNext () Java.Interop.Tools.Cecil.TypeDefinitionRocks:GetBaseType (Mono.Cecil.TypeDefinition) Mono.Cecil.TypeReference:Resolve () Mono.Cecil.ModuleDefinition:Resolve (Mono.Cecil.TypeReference) Mono.Cecil.MetadataResolver:Resolve (Mono.Cecil.TypeReference) Java.Interop.Tools.Cecil.DirectoryAssemblyResolver:Resolve (Mono.Cecil.AssemblyNameReference) Ok, this jogged my memory. Stephane Delcroix had mentioned one of the big wins for XamlC in Xamarin.Forms was to cache any time `TypeReference.Resolve()` was called: https://github.com/xamarin/Xamarin.Forms/blob/1b9c22b4b9b1c1354a3a5c35ad445a2738c6f6c3/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs#L437-L443 XamlC was able to use `static` here, because it's using a feature of MSBuild to run in a separate `AppDomain`: https://github.com/xamarin/Xamarin.Forms/blob/1b9c22b4b9b1c1354a3a5c35ad445a2738c6f6c3/Xamarin.Forms.Build.Tasks/XamlTask.cs#L20-L21 However, I think we can simply add a new non-static `TypeDefinitionCache` class that would allow callers to control the caching strategy. Callers will need to control the scope of the `TypeDefinitionCache` so it matches any `DirectoryAssemblyResolver` being used. Right now most Xamarin.Android builds will open assemblies with Mono.Cecil twice: once for `<GenerateJavaStubs/>` and once for the linker. So for example, we can add caching in an API-compatible way: [Obsolete ("Use the TypeDefinitionCache overload for better performance.")] public static TypeDefinition GetBaseType (this TypeDefinition type) => GetBaseType (type, cache: null); public static TypeDefinition GetBaseType (this TypeDefinition type, TypeDefinitionCache cache) { if (bt == null) return null; if (cache != null) return cache.Resolve (bt); return bt.Resolve (); } We just need to ensure `cache: null` is valid. I took this approach for any `public` APIs and made any `private` or `internal` APIs *require* a `TypeDefinitionCache`. I fixed every instance where `[Obsolete]` would cause a warning here in Java.Interop. We'll probably see a small improvement in `generator` and `jnimarshalmethod-gen`. Here in xamarin-android, I fixed all the `[Obsolete]` warnings that were called in `<GenerateJavaStubs/>`. I can make more changes for the linker in a future PR. ~~ Results ~~ The reduced calls to `DirectoryAssemblyResolver.Resolve`: Method call summary Total(ms) Self(ms) Calls Method name Before; 70862 97 89713 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver:Resolve (Mono.Cecil.AssemblyNameReference,Mono.Cecil.ReaderParameters) After: 68830 35 26315 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver:Resolve (Mono.Cecil.AssemblyNameReference,Mono.Cecil.ReaderParameters) ~63,398 less calls. In a build of the Xamarin.Forms integration project on macOS / Mono: Before: 1365 ms GenerateJavaStubs 1 calls After: 862 ms GenerateJavaStubs 1 calls It is almost a 40% improvement, around ~500ms better.
1 parent 4efc77d commit c49b934

File tree

2 files changed

+29
-28
lines changed

2 files changed

+29
-28
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ void Run (DirectoryAssemblyResolver res)
163163
}
164164

165165
// Step 1 - Find all the JLO types
166-
var scanner = new JavaTypeScanner (this.CreateTaskLogger ()) {
166+
var cache = new TypeDefinitionCache ();
167+
var scanner = new JavaTypeScanner (this.CreateTaskLogger (), cache) {
167168
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
168169
};
169170

@@ -175,15 +176,15 @@ void Run (DirectoryAssemblyResolver res)
175176

176177
var javaTypes = new List<TypeDefinition> ();
177178
foreach (TypeDefinition td in allJavaTypes) {
178-
if (!userAssemblies.ContainsKey (td.Module.Assembly.Name.Name) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (td)) {
179+
if (!userAssemblies.ContainsKey (td.Module.Assembly.Name.Name) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (td, cache)) {
179180
continue;
180181
}
181182

182183
javaTypes.Add (td);
183184
}
184185

185186
// Step 3 - Generate Java stub code
186-
var success = CreateJavaSources (javaTypes);
187+
var success = CreateJavaSources (javaTypes, cache);
187188
if (!success)
188189
return;
189190

@@ -199,7 +200,7 @@ void Run (DirectoryAssemblyResolver res)
199200
string managedKey = type.FullName.Replace ('/', '.');
200201
string javaKey = JavaNativeTypeManager.ToJniName (type).Replace ('/', '.');
201202

202-
acw_map.Write (type.GetPartialAssemblyQualifiedName ());
203+
acw_map.Write (type.GetPartialAssemblyQualifiedName (cache));
203204
acw_map.Write (';');
204205
acw_map.Write (javaKey);
205206
acw_map.WriteLine ();
@@ -208,14 +209,14 @@ void Run (DirectoryAssemblyResolver res)
208209
bool hasConflict = false;
209210
if (managed.TryGetValue (managedKey, out conflict)) {
210211
if (!managedConflicts.TryGetValue (managedKey, out var list))
211-
managedConflicts.Add (managedKey, list = new List<string> { conflict.GetPartialAssemblyName () });
212-
list.Add (type.GetPartialAssemblyName ());
212+
managedConflicts.Add (managedKey, list = new List<string> { conflict.GetPartialAssemblyName (cache) });
213+
list.Add (type.GetPartialAssemblyName (cache));
213214
hasConflict = true;
214215
}
215216
if (java.TryGetValue (javaKey, out conflict)) {
216217
if (!javaConflicts.TryGetValue (javaKey, out var list))
217-
javaConflicts.Add (javaKey, list = new List<string> { conflict.GetAssemblyQualifiedName () });
218-
list.Add (type.GetAssemblyQualifiedName ());
218+
javaConflicts.Add (javaKey, list = new List<string> { conflict.GetAssemblyQualifiedName (cache) });
219+
list.Add (type.GetAssemblyQualifiedName (cache));
219220
success = false;
220221
hasConflict = true;
221222
}
@@ -228,7 +229,7 @@ void Run (DirectoryAssemblyResolver res)
228229
acw_map.Write (javaKey);
229230
acw_map.WriteLine ();
230231

231-
acw_map.Write (JavaNativeTypeManager.ToCompatJniName (type).Replace ('/', '.'));
232+
acw_map.Write (JavaNativeTypeManager.ToCompatJniName (type, cache).Replace ('/', '.'));
232233
acw_map.Write (';');
233234
acw_map.Write (javaKey);
234235
acw_map.WriteLine ();
@@ -265,7 +266,7 @@ void Run (DirectoryAssemblyResolver res)
265266
manifest.NeedsInternet = NeedsInternet;
266267
manifest.InstantRunEnabled = InstantRunEnabled;
267268

268-
var additionalProviders = manifest.Merge (Log, allJavaTypes, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);
269+
var additionalProviders = manifest.Merge (Log, cache, allJavaTypes, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);
269270

270271
// Only write the new manifest if it actually changed
271272
if (manifest.SaveIfChanged (Log, MergedAndroidManifestOutput)) {
@@ -286,10 +287,10 @@ void Run (DirectoryAssemblyResolver res)
286287
StringWriter regCallsWriter = new StringWriter ();
287288
regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first.");
288289
foreach (var type in javaTypes) {
289-
if (JavaNativeTypeManager.IsApplication (type) || JavaNativeTypeManager.IsInstrumentation (type)) {
290+
if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) {
290291
string javaKey = JavaNativeTypeManager.ToJniName (type).Replace ('/', '.');
291292
regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);",
292-
type.GetAssemblyQualifiedName (), javaKey);
293+
type.GetAssemblyQualifiedName (cache), javaKey);
293294
}
294295
}
295296
regCallsWriter.Close ();
@@ -300,7 +301,7 @@ void Run (DirectoryAssemblyResolver res)
300301
template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()));
301302
}
302303

303-
bool CreateJavaSources (IEnumerable<TypeDefinition> javaTypes)
304+
bool CreateJavaSources (IEnumerable<TypeDefinition> javaTypes, TypeDefinitionCache cache)
304305
{
305306
string outputPath = Path.Combine (OutputDirectory, "src");
306307
string monoInit = GetMonoInitSource (AndroidSdkPlatform, UseSharedRuntime);
@@ -311,7 +312,7 @@ bool CreateJavaSources (IEnumerable<TypeDefinition> javaTypes)
311312
foreach (var t in javaTypes) {
312313
using (var writer = MemoryStreamPool.Shared.CreateStreamWriter ()) {
313314
try {
314-
var jti = new JavaCallableWrapperGenerator (t, Log.LogWarning) {
315+
var jti = new JavaCallableWrapperGenerator (t, Log.LogWarning, cache) {
315316
GenerateOnCreateOverrides = generateOnCreateOverrides,
316317
ApplicationJavaClass = ApplicationJavaClass,
317318
MonoRuntimeInitialization = monoInit,

src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ void ReorderActivityAliases (TaskLoggingHelper log, XElement app)
221221
}
222222
}
223223

224-
public IList<string> Merge (TaskLoggingHelper log, List<TypeDefinition> subclasses, string applicationClass, bool embed, string bundledWearApplicationName, IEnumerable<string> mergedManifestDocuments)
224+
public IList<string> Merge (TaskLoggingHelper log, TypeDefinitionCache cache, List<TypeDefinition> subclasses, string applicationClass, bool embed, string bundledWearApplicationName, IEnumerable<string> mergedManifestDocuments)
225225
{
226226
string applicationName = ApplicationName;
227227

@@ -241,7 +241,7 @@ public IList<string> Merge (TaskLoggingHelper log, List<TypeDefinition> subclass
241241
if (manifest.Attribute (androidNs + "versionName") == null)
242242
manifest.SetAttributeValue (androidNs + "versionName", "1.0");
243243

244-
app = CreateApplicationElement (manifest, applicationClass, subclasses);
244+
app = CreateApplicationElement (manifest, applicationClass, subclasses, cache);
245245

246246
if (app.Attribute (androidNs + "label") == null && applicationName != null)
247247
app.SetAttributeValue (androidNs + "label", applicationName);
@@ -296,12 +296,12 @@ public IList<string> Merge (TaskLoggingHelper log, List<TypeDefinition> subclass
296296
PackageName = t.Namespace;
297297

298298
var name = JavaNativeTypeManager.ToJniName (t).Replace ('/', '.');
299-
var compatName = JavaNativeTypeManager.ToCompatJniName (t).Replace ('/', '.');
299+
var compatName = JavaNativeTypeManager.ToCompatJniName (t, cache).Replace ('/', '.');
300300
if (((string) app.Attribute (attName)) == compatName) {
301301
app.SetAttributeValue (attName, name);
302302
}
303303

304-
Func<TypeDefinition, string, int, XElement> generator = GetGenerator (t);
304+
Func<TypeDefinition, string, int, XElement> generator = GetGenerator (t, cache);
305305
if (generator == null)
306306
continue;
307307

@@ -395,7 +395,7 @@ public IList<string> Merge (TaskLoggingHelper log, List<TypeDefinition> subclass
395395
}
396396
}
397397

398-
AddInstrumentations (manifest, subclasses, targetSdkVersionValue);
398+
AddInstrumentations (manifest, subclasses, targetSdkVersionValue, cache);
399399
AddPermissions (app);
400400
AddPermissionGroups (app);
401401
AddPermissionTrees (app);
@@ -492,20 +492,20 @@ IEnumerable<XNode> FixupNameElements(string packageName, IEnumerable<XNode> node
492492
return nodes;
493493
}
494494

495-
Func<TypeDefinition, string, int, XElement> GetGenerator (TypeDefinition type)
495+
Func<TypeDefinition, string, int, XElement> GetGenerator (TypeDefinition type, TypeDefinitionCache cache)
496496
{
497-
if (type.IsSubclassOf ("Android.App.Activity"))
497+
if (type.IsSubclassOf ("Android.App.Activity", cache))
498498
return ActivityFromTypeDefinition;
499-
if (type.IsSubclassOf ("Android.App.Service"))
499+
if (type.IsSubclassOf ("Android.App.Service", cache))
500500
return (t, name, v) => ToElement (t, name, ServiceAttribute.FromTypeDefinition, x => x.ToElement (PackageName));
501-
if (type.IsSubclassOf ("Android.Content.BroadcastReceiver"))
501+
if (type.IsSubclassOf ("Android.Content.BroadcastReceiver", cache))
502502
return (t, name, v) => ToElement (t, name, BroadcastReceiverAttribute.FromTypeDefinition, x => x.ToElement (PackageName));
503-
if (type.IsSubclassOf ("Android.Content.ContentProvider"))
503+
if (type.IsSubclassOf ("Android.Content.ContentProvider", cache))
504504
return (t, name, v) => ToProviderElement (t, name);
505505
return null;
506506
}
507507

508-
XElement CreateApplicationElement (XElement manifest, string applicationClass, List<TypeDefinition> subclasses)
508+
XElement CreateApplicationElement (XElement manifest, string applicationClass, List<TypeDefinition> subclasses, TypeDefinitionCache cache)
509509
{
510510
var application = manifest.Descendants ("application").FirstOrDefault ();
511511

@@ -534,7 +534,7 @@ XElement CreateApplicationElement (XElement manifest, string applicationClass, L
534534
if (aa == null)
535535
continue;
536536

537-
if (!t.IsSubclassOf ("Android.App.Application"))
537+
if (!t.IsSubclassOf ("Android.App.Application", cache))
538538
throw new InvalidOperationException (string.Format ("Found [Application] on type {0}. [Application] can only be used on subclasses of Application.", t.FullName));
539539

540540
typeAttr.Add (aa);
@@ -860,7 +860,7 @@ void AddSupportsGLTextures (XElement application)
860860
}
861861
}
862862

863-
void AddInstrumentations (XElement manifest, IList<TypeDefinition> subclasses, int targetSdkVersion)
863+
void AddInstrumentations (XElement manifest, IList<TypeDefinition> subclasses, int targetSdkVersion, TypeDefinitionCache cache)
864864
{
865865
var assemblyAttrs =
866866
Assemblies.SelectMany (path => InstrumentationAttribute.FromCustomAttributeProvider (Resolver.GetAssembly (path)));
@@ -874,7 +874,7 @@ void AddInstrumentations (XElement manifest, IList<TypeDefinition> subclasses, i
874874
}
875875

876876
foreach (var type in subclasses)
877-
if (type.IsSubclassOf ("Android.App.Instrumentation")) {
877+
if (type.IsSubclassOf ("Android.App.Instrumentation", cache)) {
878878
var xe = InstrumentationFromTypeDefinition (type, JavaNativeTypeManager.ToJniName (type).Replace ('/', '.'), targetSdkVersion);
879879
if (xe != null)
880880
manifest.Add (xe);

0 commit comments

Comments
 (0)