Skip to content

Commit

Permalink
POC for faster instantiation of NSObject subclasses by using factory …
Browse files Browse the repository at this point in the history
…classes instead of reflection.

Idea: create an instance of a factory class that is able to create an instance
of the desired type without using reflection.

This POC is hardcoded to only implement support for UIView (since that's what
the test case is creating). If productized, the factory class would have to be
created by the generator.

Numbers are impressive: ~40% faster.

Downsides: a lot of extra generated code (bloat) - i.e. bigger apps.

Numbers
=======

Test case: rolfbjarne/TestApp@004283d

Fix 1 refers to PR xamarin#5009.
Fix 2 refers to PR xamarin#5013.
Fix 3 refers to PR xamarin#5016.
Fix 4 refers to PR xamarin#5017.
Fix 5 is this fix.

iPad Air 2
----------

| Configuration       | Before | After fix 1 | After fix 2  | After fix 3  | After fix 4  | After fix 5  | Improvement from fix 4 to fix 5 | Cumulative improvement |
| ------------------- | ------ | ----------: | -----------: | -----------: | -----------: | -----------: | ------------------------------: | ---------------------: |
| Release (link all)  | 477 ms |      481 ms |       224 ms |       172 ms |       148 ms |        86 ms |                     62 ms (42%) |           391 ms (82%) |
| Release (dont link) | 738 ms |      656 ms |       377 ms |       201 ms |       146 ms |        88 ms |                     58 ms (40%) |           650 ms (88%) |

iPhone X
--------

| Configuration       | Before | After fix 1 | After fix 2  | After fix 3  | After fix 4  | After fix 5  | Improvement from fix 4 to fix 5 | Cumulative improvement |
| ------------------- | ------ | ----------: | -----------: | -----------: | -----------: | -----------: | ------------------------------: | ---------------------: |
| Release (link all)  |  98 ms |       99 ms |        42 ms |        31 ms |        29 ms |        18 ms |                     11 ms (38%) |            80 ms (82%) |
| Release (dont link) | 197 ms |      153 ms |        91 ms |        43 ms |        28 ms |        17 ms |                     11 ms (39%) |           180 ms (91%) |

When linking all assemblies, the type map has 24 entries, and when not linking
at all it has 2993 entries.
  • Loading branch information
rolfbjarne committed Oct 22, 2018
1 parent 46a242c commit c6c9e2a
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 8 deletions.
31 changes: 23 additions & 8 deletions src/ObjCRuntime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
#endif
#endif

namespace ObjCRuntime {
public interface INSObjectInstantiator
{
NSObject Create (IntPtr handle);
}
}

namespace ObjCRuntime {

public partial class Runtime {
Expand All @@ -39,7 +46,7 @@ public partial class Runtime {
#endif

static Dictionary<IntPtrTypeValueTuple,Delegate> block_to_delegate_cache;
static Dictionary<Type, ConstructorInfo> intptr_ctor_cache;
static Dictionary<Type, Tuple<ConstructorInfo,INSObjectInstantiator>> intptr_ctor_cache;
static Dictionary<Type, ConstructorInfo> intptr_bool_ctor_cache;

static List <object> delegates;
Expand Down Expand Up @@ -249,7 +256,7 @@ unsafe static void Initialize (InitializationOptions* options)
Runtime.options = options;
delegates = new List<object> ();
object_map = new Dictionary <IntPtr, WeakReference> (IntPtrEqualityComparer);
intptr_ctor_cache = new Dictionary<Type, ConstructorInfo> (TypeEqualityComparer);
intptr_ctor_cache = new Dictionary<Type, Tuple<ConstructorInfo,INSObjectInstantiator>> (TypeEqualityComparer);
intptr_bool_ctor_cache = new Dictionary<Type, ConstructorInfo> (TypeEqualityComparer);
lock_obj = new object ();

Expand Down Expand Up @@ -1135,13 +1142,16 @@ internal static T ConstructNSObject<T> (IntPtr ptr) where T: NSObject
if (type == null)
throw new ArgumentNullException ("type");

var ctor = GetIntPtrConstructor (type);

if (ctor == null) {
var tuple = GetIntPtrConstructor (type);
if (tuple == null || (tuple.Item1 == null && tuple.Item2 == null)) {
MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, selector, method);
return null;
}

if (tuple.Item2 != null)
return (T) (object) tuple.Item2.Create (ptr);

var ctor = tuple?.Item1;
return (T) ctor.Invoke (new object[] { ptr });
}

Expand All @@ -1162,7 +1172,7 @@ static T ConstructINativeObject<T> (IntPtr ptr, bool owns, Type type, MissingCto
return (T) ctor.Invoke (new object[] { ptr, owns});
}

static ConstructorInfo GetIntPtrConstructor (Type type)
static Tuple<ConstructorInfo, INSObjectInstantiator> GetIntPtrConstructor (Type type)
{
lock (intptr_ctor_cache) {
if (intptr_ctor_cache.TryGetValue (type, out var rv))
Expand All @@ -1172,9 +1182,14 @@ static ConstructorInfo GetIntPtrConstructor (Type type)
for (int i = 0; i < ctors.Length; ++i) {
var param = ctors[i].GetParameters ();
if (param.Length == 1 && param [0].ParameterType == typeof (IntPtr)) {
var ctor = ctors [i];
var instantiator = (INSObjectInstantiator) type.GetMethod ("CreateInstantiator", BindingFlags.NonPublic | BindingFlags.Static)?.Invoke (null, null);
var tuple = new Tuple<ConstructorInfo, INSObjectInstantiator> (ctors [i], instantiator);
lock (intptr_ctor_cache)
intptr_ctor_cache [type] = ctors [i];
return ctors [i];
intptr_ctor_cache [type] = tuple;
if (instantiator != null)
Console.WriteLine ("Found instantiator for: {0}", type.FullName);
return tuple;
}
}
return null;
Expand Down
13 changes: 13 additions & 0 deletions src/UIKit/UIView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
namespace UIKit {
public partial class UIView : IEnumerable {

class Instantiator : INSObjectInstantiator
{
public NSObject Create (IntPtr handle)
{
return new UIView (handle);
}
}
[Preserve (Conditional = true)]
static INSObjectInstantiator CreateInstantiator ()
{
return new Instantiator ();
}

public void Add (UIView view)
{
AddSubview (view);
Expand Down

0 comments on commit c6c9e2a

Please sign in to comment.