Skip to content

Commit 3cf86c8

Browse files
jonathanpeppersjonpryor
authored andcommitted
[Java.Interop] remove IEnumerable iterators called during startup (#555)
Context: dotnet/android#4102 When profiling with `adb shell setprop debug.mono.profile log:alloc` against the [Xamarin.Forms integration project in xamarin-android][0], I noticed the following allocations: Allocation summary Bytes Count Average Type name 84840 505 168 Java.Interop.JniRuntime.JniTypeManager.<CreateGetTypeSignaturesEnumerator>d__11 24096 502 48 Android.Runtime.AndroidTypeManager.<GetSimpleReferences>d__1 100K of memory allocations are a bit much, so seemed like it might help to reduce these. To reduce the allocations, update `JniTypeManager.GetTypeSignature()` to *not* call `JniTypeManager.GetTypeSignatures()` (note: plural) and instead "duplicate" the `GetTypeSignatures()` logic, optimizing for a single return value. `JniTypeManager.GetTypeSignature()` calls the new method `JniTypeManager.GetSimpleReference()`. There are thus two methods of consequence derived types can override: partial class JniTypeManager { protected virtual string GetSimpleReference (Type type); protected virtual IEnumerable<string> GetSimpleReferences (Type type); } Which one should be overridden? `GetSimpleReference()` is implemented in terms of `GetSimpleReferences()`, so the only method that is "required" to be overridden is `GetSimpleReferences()`. However, when concerned about performance, *both* methods should be overridden, taking care to ensure that `GetSimpleReference()` returns the *first* value that `GetSimpleReferences()` would return. Other general performance changes: * Added `[MethodImpl(MethodImplOptions.AggressiveInlining)]` to `JniTypeManager.AssertValid()`. It is called quite frequently. * Removed a call to `.GetTypeInfo()`, as this was not needed. It [wraps `System.Type` in another type][1] that was probably originally used for netstandard 1.x compatibility. ~~ Results ~~ After [updating xamarin-android][2] to override `JniTypeManager.GetSimpleReference()`, I was able to see a startup improvement in a Release build of the Xamarin.Forms integration project on a Pixel 3 XL: * Before: 01-08 14:53:34.926 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +779ms 01-08 14:53:38.892 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +764ms 01-08 14:53:42.859 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +753ms 01-08 14:53:46.795 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +748ms 01-08 14:53:50.792 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +764ms 01-08 14:53:54.759 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +756ms 01-08 14:53:58.726 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +764ms 01-08 14:54:02.675 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +749ms 01-08 14:54:06.691 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +759ms 01-08 14:54:10.641 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +749ms * Average(ms): 758.5 * Std Err(ms): 3.0523215208537 * Std Dev(ms): 9.65228815704684 * After: 01-08 14:55:56.972 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +772ms 01-08 14:56:00.906 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +741ms 01-08 14:56:05.006 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +741ms 01-08 14:56:08.940 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +748ms 01-08 14:56:12.890 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +747ms 01-08 14:56:16.839 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +753ms 01-08 14:56:20.824 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +757ms 01-08 14:56:24.775 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +746ms 01-08 14:56:28.776 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +747ms 01-08 14:56:32.740 1473 1503 I ActivityTaskManager: Displayed Xamarin.Forms_Performance_Integration/xamarin.forms.performance.integration.MainActivity: +748ms * Average(ms): 750 * Std Err(ms): 2.87904305089189 * Std Dev(ms): 9.10433352249844 It seems like this might save ~8ms on startup? There is also a good improvement to memory usage/allocations. Looking at Mono's memory summary: * Before: * Working Set : 71008256 * Private Bytes : 601202688 * Virtual Bytes : 1306873856 * After: * Working Set : 70774784 * Private Bytes : 552910848 * Virtual Bytes : 1302822912 Then the total GC summary: * Before: Event: GC allocations : 9340 * After: Event: GC allocations : 8867 ~473 allocations are saved. [0]: https://github.com/xamarin/xamarin-android/tree/master/tests/Xamarin.Forms-Performance-Integration [1]: https://github.com/mono/mono/blob/c5b88ec4f323f2bdb7c7d0a595ece28dae66579c/mcs/class/referencesource/mscorlib/system/reflection/introspectionextensions.cs#L24 [2]: dotnet/android#4102
1 parent f6babec commit 3cf86c8

File tree

1 file changed

+83
-29
lines changed

1 file changed

+83
-29
lines changed

src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq;
55
using System.Reflection;
6+
using System.Runtime.CompilerServices;
67
using System.Threading;
78

89
namespace Java.Interop {
@@ -32,48 +33,80 @@ protected virtual void Dispose (bool disposing)
3233
disposed = true;
3334
}
3435

36+
[MethodImpl (MethodImplOptions.AggressiveInlining)]
3537
void AssertValid ()
3638
{
3739
if (!disposed)
3840
return;
3941
throw new ObjectDisposedException (nameof (JniTypeManager));
4042
}
4143

44+
// NOTE: This method needs to be kept in sync with GetTypeSignatures()
45+
// This version of the method has removed IEnumerable for performance reasons.
4246
public JniTypeSignature GetTypeSignature (Type type)
4347
{
4448
AssertValid ();
4549

46-
return GetTypeSignatures (type).FirstOrDefault ();
50+
if (type == null)
51+
throw new ArgumentNullException (nameof (type));
52+
if (type.ContainsGenericParameters)
53+
throw new ArgumentException ($"'{type}' contains a generic type definition. This is not supported.", nameof (type));
54+
55+
type = GetUnderlyingType (type, out int rank);
56+
57+
foreach (var mapping in JniBuiltinTypeNameMappings.Value) {
58+
if (mapping.Key == type) {
59+
var r = mapping.Value;
60+
return r.AddArrayRank (rank);
61+
}
62+
}
63+
64+
foreach (var mapping in JniBuiltinArrayMappings.Value) {
65+
if (mapping.Key == type) {
66+
var r = mapping.Value;
67+
return r.AddArrayRank (rank);
68+
}
69+
}
70+
71+
var name = type.GetCustomAttribute<JniTypeSignatureAttribute> (inherit: false);
72+
if (name != null) {
73+
return new JniTypeSignature (name.SimpleReference, name.ArrayRank + rank, name.IsKeyword);
74+
}
75+
76+
var isGeneric = type.IsGenericType;
77+
var genericDef = isGeneric ? type.GetGenericTypeDefinition () : type;
78+
if (isGeneric) {
79+
if (genericDef == typeof (JavaArray<>) || genericDef == typeof (JavaObjectArray<>)) {
80+
var r = GetTypeSignature (type.GenericTypeArguments [0]);
81+
return r.AddArrayRank (rank + 1);
82+
}
83+
}
84+
85+
var simpleRef = GetSimpleReference (type);
86+
if (simpleRef != null)
87+
return new JniTypeSignature (simpleRef, rank, false);
88+
89+
if (isGeneric) {
90+
simpleRef = GetSimpleReference (genericDef);
91+
if (simpleRef != null)
92+
return new JniTypeSignature (simpleRef, rank, false);
93+
}
94+
95+
return default;
4796
}
4897

98+
// NOTE: This method needs to be kept in sync with GetTypeSignature()
4999
public IEnumerable<JniTypeSignature> GetTypeSignatures (Type type)
50100
{
51101
AssertValid ();
52102

53103
if (type == null)
54104
throw new ArgumentNullException (nameof (type));
55-
if (type.GetTypeInfo ().ContainsGenericParameters)
56-
throw new ArgumentException ("Generic type definitions are not supported.", nameof (type));
105+
if (type.ContainsGenericParameters)
106+
throw new ArgumentException ($"'{type}' contains a generic type definition. This is not supported.", nameof (type));
57107

58-
return CreateGetTypeSignaturesEnumerator (type);
59-
}
108+
type = GetUnderlyingType (type, out int rank);
60109

61-
IEnumerable<JniTypeSignature> CreateGetTypeSignaturesEnumerator (Type type)
62-
{
63-
var originalType = type;
64-
int rank = 0;
65-
while (type.IsArray) {
66-
if (type.IsArray && type.GetArrayRank () > 1)
67-
throw new ArgumentException ("Multidimensional array '" + originalType.FullName + "' is not supported.", nameof (type));
68-
rank++;
69-
type = type.GetElementType ();
70-
}
71-
72-
var info = type.GetTypeInfo ();
73-
if (info.IsEnum)
74-
type = Enum.GetUnderlyingType (type);
75-
76-
#if !XA_INTEGRATION
77110
foreach (var mapping in JniBuiltinTypeNameMappings.Value) {
78111
if (mapping.Key == type) {
79112
var r = mapping.Value;
@@ -87,23 +120,21 @@ IEnumerable<JniTypeSignature> CreateGetTypeSignaturesEnumerator (Type type)
87120
yield return r.AddArrayRank (rank);
88121
}
89122
}
90-
#endif // !XA_INTEGRATION
91123

92-
var name = info.GetCustomAttribute<JniTypeSignatureAttribute> (inherit: false);
124+
var name = type.GetCustomAttribute<JniTypeSignatureAttribute> (inherit: false);
93125
if (name != null) {
94126
yield return new JniTypeSignature (name.SimpleReference, name.ArrayRank + rank, name.IsKeyword);
95127
}
96128

97-
var isGeneric = info.IsGenericType;
98-
var genericDef = isGeneric ? info.GetGenericTypeDefinition () : type;
99-
#if !XA_INTEGRATION
129+
var isGeneric = type.IsGenericType;
130+
var genericDef = isGeneric ? type.GetGenericTypeDefinition () : type;
100131
if (isGeneric) {
101132
if (genericDef == typeof(JavaArray<>) || genericDef == typeof(JavaObjectArray<>)) {
102-
var r = GetTypeSignature (info.GenericTypeArguments [0]);
133+
var r = GetTypeSignature (type.GenericTypeArguments [0]);
103134
yield return r.AddArrayRank (rank + 1);
104135
}
105136
}
106-
#endif // !XA_INTEGRATION
137+
107138
foreach (var simpleRef in GetSimpleReferences (type)) {
108139
if (simpleRef == null)
109140
continue;
@@ -119,6 +150,29 @@ IEnumerable<JniTypeSignature> CreateGetTypeSignaturesEnumerator (Type type)
119150
}
120151
}
121152

153+
static Type GetUnderlyingType (Type type, out int rank)
154+
{
155+
rank = 0;
156+
var originalType = type;
157+
while (type.IsArray) {
158+
if (type.IsArray && type.GetArrayRank () > 1)
159+
throw new ArgumentException ("Multidimensional array '" + originalType.FullName + "' is not supported.", nameof (type));
160+
rank++;
161+
type = type.GetElementType ();
162+
}
163+
164+
if (type.IsEnum)
165+
type = Enum.GetUnderlyingType (type);
166+
167+
return type;
168+
}
169+
170+
// `type` will NOT be an array type.
171+
protected virtual string GetSimpleReference (Type type)
172+
{
173+
return GetSimpleReferences (type).FirstOrDefault ();
174+
}
175+
122176
// `type` will NOT be an array type.
123177
protected virtual IEnumerable<string> GetSimpleReferences (Type type)
124178
{

0 commit comments

Comments
 (0)