From c6c9e2a8ba2fe5ae8ca8b38027c84b902473fa7b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 19 Oct 2018 12:07:22 +0200 Subject: [PATCH] POC for faster instantiation of NSObject subclasses by using factory 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: https://github.com/rolfbjarne/TestApp/commit/004283d7b628a29fcf711d98d8842bfd4ef4393b Fix 1 refers to PR #5009. Fix 2 refers to PR #5013. Fix 3 refers to PR #5016. Fix 4 refers to PR #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. --- src/ObjCRuntime/Runtime.cs | 31 +++++++++++++++++++++++-------- src/UIKit/UIView.cs | 13 +++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index ab4a161aa2bb..316c0277bd06 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -28,6 +28,13 @@ #endif #endif +namespace ObjCRuntime { + public interface INSObjectInstantiator + { + NSObject Create (IntPtr handle); + } +} + namespace ObjCRuntime { public partial class Runtime { @@ -39,7 +46,7 @@ public partial class Runtime { #endif static Dictionary block_to_delegate_cache; - static Dictionary intptr_ctor_cache; + static Dictionary> intptr_ctor_cache; static Dictionary intptr_bool_ctor_cache; static List delegates; @@ -249,7 +256,7 @@ unsafe static void Initialize (InitializationOptions* options) Runtime.options = options; delegates = new List (); object_map = new Dictionary (IntPtrEqualityComparer); - intptr_ctor_cache = new Dictionary (TypeEqualityComparer); + intptr_ctor_cache = new Dictionary> (TypeEqualityComparer); intptr_bool_ctor_cache = new Dictionary (TypeEqualityComparer); lock_obj = new object (); @@ -1135,13 +1142,16 @@ internal static T ConstructNSObject (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 }); } @@ -1162,7 +1172,7 @@ static T ConstructINativeObject (IntPtr ptr, bool owns, Type type, MissingCto return (T) ctor.Invoke (new object[] { ptr, owns}); } - static ConstructorInfo GetIntPtrConstructor (Type type) + static Tuple GetIntPtrConstructor (Type type) { lock (intptr_ctor_cache) { if (intptr_ctor_cache.TryGetValue (type, out var rv)) @@ -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 (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; diff --git a/src/UIKit/UIView.cs b/src/UIKit/UIView.cs index 6930e1c69c79..15611c4cede3 100644 --- a/src/UIKit/UIView.cs +++ b/src/UIKit/UIView.cs @@ -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);