Skip to content

Commit

Permalink
[foundation] Cache parts of NSObject.ConformsToProtocol
Browse files Browse the repository at this point in the history
Note that the call to native code still _always_ happen (not cached) since the application could use `class_addProtocol` to add conformance to a protocol at runtime.

So the cache is limited to the .net specific reflection code that is present (only) when the dynamic registrar is included inside applications. This is the default for macOS apps, but not iOS / tvOS or MacCatalyst apps.

The linker/trimmer will remove the caching code when the dynamic registrar is removed. IOW this PR should not have any impact, performance or size, for most iOS apps (where the dynamic registrar is removed by default).

Fix xamarin#14065

Running Dope on macOS, a 2 minutes benchmark, shows the following times (in seconds and percentage) spent calling this API:

**Before**

* `RemoveFromSuperview` 7.99s (6.4%)
  * `NSObject.ConformsToProtocol` 3.26s (2.6%)

**After**

* `RemoveFromSuperview` 4.67s (3.8%)
  * `NSObject.ConformsToProtocol` 0.32s (.26%)

So a 10x improvements on `ConformsToProtocol` which helps a lot the code path calling `RemoveFromSuperview`.
  • Loading branch information
spouliot committed Jun 17, 2022
1 parent ab52bb2 commit ddf76f4
Showing 1 changed file with 30 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/Foundation/NSObject2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,36 @@ public virtual bool ConformsToProtocol (NativeHandle protocol)
if (!Runtime.DynamicRegistrationSupported)
return false;

// the linker/trimmer will remove the following code if the dynamic registrar is removed from the app
var classHandle = ClassHandle;
bool new_map = false;
lock (protocol_cache) {
#if NET
ref var map = ref CollectionsMarshal.GetValueRefOrAddDefault (protocol_cache, classHandle, out var exists);
if (!exists)
map = new ();
ref var result = ref CollectionsMarshal.GetValueRefOrAddDefault (map, protocol, out exists);
if (!exists)
result = DynamicConformsToProtocol (protocol);
#else
if (!protocol_cache.TryGetValue (classHandle, out var map)) {
map = new ();
new_map = true;
protocol_cache.Add (classHandle, map);
}
if (new_map || !map.TryGetValue (protocol, out var result)) {
result = DynamicConformsToProtocol (protocol);
map.Add (protocol, result);
}
#endif
return result;
}
}

static Dictionary<NativeHandle, Dictionary<NativeHandle, bool>> protocol_cache = new ();

bool DynamicConformsToProtocol (NativeHandle protocol)
{
object [] adoptedProtocols = GetType ().GetCustomAttributes (typeof (AdoptsAttribute), true);
foreach (AdoptsAttribute adopts in adoptedProtocols){
if (adopts.ProtocolHandle == protocol)
Expand Down

0 comments on commit ddf76f4

Please sign in to comment.