Reducing the number of JNI global references (GREFs) in Xamarin.Android apps can noticeably improve performance of the garbage collector. Each instance of a Managed Callable Wrapper,
a Managed Callable Wrapper subclass, or an Android Callable Wrapper
will create one GREF. The garbage collection docs call these object types peer objects.
The fewer peer objects, the fewer GREFs, and the more quickly the GC will run. So to a first approximation, apps that do as much as possible in pure C# will have better performance.
BaseAdapter
instead of ArrayAdapter
One trick that will help reduce unnecessary [Java – C#] bridge interaction
is to subclass BaseAdapter
rather than ArrayAdapter
, and then use pure C# objects (managed
objects rather than peer objects
) within the adapter. That way, instead of many individual wrappers for each C# object in the array, the GC only has to consider one big
wrapper for the whole array.
This talk from last year's Evolve carefully explains the problem in slides 31-36. These slides start around 30:30. Note that slide 32 is missing from the video. Slide 32 should be displayed at 32:50. Slide 34 should start at 35:31.
ArrayAdapter
The Remarks
section of this API docs page also discusses the issue.
This blog post discusses several tips for ListView performance, including the BaseAdapter
trick briefly near the very end.
A similar idea applies to any C# subclass of a Managed Callable Wrapper that has many object references (of any type). Where possible, it's advantageous to aggregate the individual references into a container object, and then reference that single container object instead.
As a counter-example, it's OK to have a Java.Util.ArrayList
that is initialized with many non-subclassed MCW types. Once the ArrayList
is created, the MCW types are no longer needed, so they can be disposed. And as soon as they're disposed, the C# garbage collector will ignore them.
public static class MyClass { public static void TestGrefs() { var arrayList = new Java.Util.ArrayList(500); for (var i = 0; i < 500; i++) { arrayList.Add(new Android.Graphics.Point(0, i)); } } }
public static class MyClass { public static void TestGrefs() { var arrayList = new Java.Util.ArrayList(500); for (var i = 0; i < 500; i++) { using (var point = new Android.Graphics.Point(0, i)) { arrayList.Add(point); } } } }
Initializing the array is more costly than initializing a pure managed
C# array.
Accessing elements from the array is more costly than accessing pure managed
C# elements from a pure managed
C# array.
Adding subclassed MCW types to the array is problematic because if the subclass is actually a managed subclass, e.g. a custom Activity, then disposing of the instance will break the mapping.
That is, we can't dispose MCW subclasses used to initialize the array because we might need to call back to them later.
Adding pure C# types to the array is similarly problematic.
Submit a comment or correction
24 May 2014 | Posted |