Skip to content

Commit

Permalink
[perf] Cache Runtime.IsUserType results (#15149)
Browse files Browse the repository at this point in the history
This is computed **twice** _per instance_, when the instance is
* created: `NSObject.CreateManagedRef`
* released: `NSObject.ReleaseManagedRef`

However its (boolean) value remains identical for all instances of the same type.

This shows up as consuming a lot of time in the logs attached to #15145

Basically two things are cached
* the selector for `xamarinSetGCHandle:flags:`, which makes it 15% faster;
	 * some platforms, but not macOS, have an optimization for the `Selector.GetHandle` API
* the result is cached into a `Dictionrary<IntPtr,bool>`

Note that the optimizations are not specific to CoreCLR nor macOS (so they are not fixes for the CoreCLR performance regression of the above mentioned issue).
OTOH it will also help performance for legacy and other net6 (mono) platforms.

```
BenchmarkDotNet=v0.12.1, OS=macOS 12.3.1 (21E258) [Darwin 21.4.0]
Apple M1, 1 CPU, 8 logical and 8 physical cores
.NET Core SDK=        6.0.100 [/usr/local/share/dotnet/sdk]
  [Host] : .NET Core 6.0 (CoreCLR 6.0.522.21309, CoreFX 6.0.522.21309), Arm64 RyuJIT

Job=InProcess  Toolchain=InProcessEmitToolchain  IterationCount=3
LaunchCount=1  WarmupCount=3
```

|           Method | Length |           Mean |        Error |      StdDev | Ratio |
|----------------- |------- |---------------:|-------------:|------------:|------:|
|         Original |     16 |     7,729.8 ns |    212.61 ns |    11.65 ns |  1.00 |
|   CachedSelector |     16 |     6,552.6 ns |    202.70 ns |    11.11 ns |  0.85 |
| CachedIsUserType |     16 |       162.0 ns |     14.86 ns |     0.81 ns |  0.02 |
|                  |        |                |              |             |       |
|         Original |    256 |   123,183.0 ns |  4,724.95 ns |   258.99 ns |  1.00 |
|   CachedSelector |    256 |   104,570.3 ns |  2,029.20 ns |   111.23 ns |  0.85 |
| CachedIsUserType |    256 |     2,489.5 ns |    390.86 ns |    21.42 ns |  0.02 |
|                  |        |                |              |             |       |
|         Original |   4096 | 1,970,381.7 ns | 66,393.09 ns | 3,639.23 ns |  1.00 |
|   CachedSelector |   4096 | 1,676,773.0 ns | 12,149.92 ns |   665.98 ns |  0.85 |
| CachedIsUserType |   4096 |    39,933.3 ns |  7,426.74 ns |   407.08 ns |  0.02 |

[Benchmark source code](https://gist.github.com/spouliot/42fd43e94c5a9ce90164f0f6f9b35018)
  • Loading branch information
spouliot authored Jun 1, 2022
1 parent 8fff154 commit 33b73a1
Showing 1 changed file with 27 additions and 2 deletions.
29 changes: 27 additions & 2 deletions src/ObjCRuntime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public partial class Runtime {
static List <object> delegates;
static List <Assembly> assemblies;
static Dictionary <IntPtr, GCHandle> object_map;
static Dictionary <IntPtr, bool> usertype_cache;
static object lock_obj;
static IntPtr NSObjectClass;
static bool initialized;
Expand Down Expand Up @@ -272,6 +273,7 @@ unsafe static void Initialize (InitializationOptions* options)
Runtime.options = options;
delegates = new List<object> ();
object_map = new Dictionary <IntPtr, GCHandle> (IntPtrEqualityComparer);
usertype_cache = new Dictionary <IntPtr, bool> (IntPtrEqualityComparer);
intptr_ctor_cache = new Dictionary<Type, ConstructorInfo> (TypeEqualityComparer);
intptr_bool_ctor_cache = new Dictionary<Type, ConstructorInfo> (TypeEqualityComparer);
lock_obj = new object ();
Expand Down Expand Up @@ -1735,11 +1737,31 @@ internal static IntPtr GetProtocolForType (Type type)
throw new ArgumentException ($"'{type.FullName}' is an unknown protocol");
}

[BindingImpl (BindingImplOptions.Optimizable)]
internal static bool IsUserType (IntPtr self)
{
var cls = Class.object_getClass (self);
lock (usertype_cache) {
#if NET
ref var result = ref CollectionsMarshal.GetValueRefOrAddDefault (usertype_cache, cls, out var exists);
if (!exists)
result = SlowIsUserType (cls);
#else
if (!usertype_cache.TryGetValue (cls, out var result)) {
result = SlowIsUserType (cls);
usertype_cache.Add (cls, result);
}
#endif
return result;
}
}

#if __MACOS__
static IntPtr selSetGCHandle = Selector.GetHandle ("xamarinSetGCHandle:flags:");
#endif

[BindingImpl (BindingImplOptions.Optimizable)]
static bool SlowIsUserType (IntPtr cls)
{
unsafe {
if (options->RegistrationMap is not null && options->RegistrationMap->map_count > 0) {
var map = options->RegistrationMap->map;
Expand All @@ -1752,8 +1774,11 @@ internal static bool IsUserType (IntPtr self)
return false;
}
}

#if __MACOS__
return Class.class_getInstanceMethod (cls, selSetGCHandle) != IntPtr.Zero;
#else
return Class.class_getInstanceMethod (cls, Selector.GetHandle ("xamarinSetGCHandle:flags:")) != IntPtr.Zero;
#endif
}

static unsafe int FindUserTypeIndex (MTClassMap* map, int lo, int hi, IntPtr cls)
Expand Down

16 comments on commit 33b73a1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📚 [CI Build] Artifacts 📚

Artifacts were not provided.

Pipeline on Agent XAMMINI-053.Monterey
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💻 [CI Build] Tests on macOS Mac Catalina (10.15) passed 💻

All tests on macOS Mac Catalina (10.15) passed.

Pipeline on Agent
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💻 [CI Build] Tests on macOS Mac Catalina (10.15) passed 💻

All tests on macOS Mac Catalina (10.15) passed.

Pipeline on Agent
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💻 [CI Build] Tests on macOS Mac Catalina (10.15) passed 💻

All tests on macOS Mac Catalina (10.15) passed.

Pipeline on Agent
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💻 [CI Build] Tests on macOS M1 - Mac Big Sur (11.5) passed 💻

All tests on macOS M1 - Mac Big Sur (11.5) passed.

Pipeline on Agent
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 [CI Build] API Diff 📋

API diff (for current PR)

ℹ️ API Diff (from PR only) (please review changes)

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

API diff (vs stable)

✅ API Diff from stable

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

Generator diff

Generator Diff (no change)

Pipeline on Agent XAMMINI-054.Monterey
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 [CI Build] API Diff 📋

API diff (for current PR)

ℹ️ API Diff (from PR only) (please review changes)

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

API diff (vs stable)

✅ API Diff from stable

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

Generator diff

Generator Diff (no change)

Pipeline on Agent XAMMINI-051.Monterey'
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 [CI Build] API Diff 📋

API diff (for current PR)

ℹ️ API Diff (from PR only) (please review changes)

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

API diff (vs stable)

✅ API Diff from stable

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

Generator diff

Generator Diff (no change)

Pipeline on Agent XAMMINI-061.Monterey
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ [CI Build] Tests on macOS M1 - Mac Big Sur (11.5) failed ❌

Failed tests are:

  • introspection
  • monotouch-test

Pipeline on Agent
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 [CI Build] API Diff 📋

API diff (for current PR)

ℹ️ API Diff (from PR only) (please review changes)

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

API diff (vs stable)

✅ API Diff from stable

API diff: vsdrops gist

Xamarin
.NET
Xamarin vs .NET
iOS vs Mac Catalyst (.NET)

Generator diff

Generator Diff (no change)

Pipeline on Agent XAMMINI-055.Monterey'
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ [CI Build] Tests on macOS M1 - Mac Big Sur (11.5) failed ❌

Failed tests are:

  • introspection

Pipeline on Agent
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💻 [CI Build] Tests on macOS Mac Catalina (10.15) passed 💻

All tests on macOS Mac Catalina (10.15) passed.

Pipeline on Agent
Hash: 33b73a1fd6f2707558f820af884347b8d36f3b42

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ [CI Build] Tests failed on VSTS: simulator tests iOS ❌

Tests failed on VSTS: simulator tests iOS.

Test results

8 tests failed, 226 tests passed.

Failed tests

  • fsharp/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemXmlTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemDataTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemWebServicesTests/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono MicrosoftCSharpXunit/watchOS 32-bits - simulator/Debug: Crashed
  • mscorlib Part 2/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemCoreXunit Part 1/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemCoreXunit Part 2/watchOS 32-bits - simulator/Debug: Crashed

Pipeline on Agent XAMBOT-1108.Monterey'
[perf] Cache Runtime.IsUserType results (#15149)

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ [CI Build] Tests failed on VSTS: simulator tests iOS ❌

Tests failed on VSTS: simulator tests iOS.

Test results

13 tests failed, 221 tests passed.

Failed tests

  • xcframework-test/watchOS 32-bits - simulator/Debug: Crashed
  • link sdk/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemCoreTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemTransactionsTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemXmlTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemComponentModelDataAnnotationsTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemIOCompressionTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemSecurityTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemWebServicesTests/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono MicrosoftCSharpXunit/watchOS 32-bits - simulator/Debug: Crashed
  • mscorlib Part 2/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemCoreXunit Part 1/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemXunit/watchOS 32-bits - simulator/Debug: Crashed

Pipeline on Agent XAMBOT-1042.Monterey'
[perf] Cache Runtime.IsUserType results (#15149)

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ [CI Build] Tests failed on VSTS: simulator tests iOS ❌

Tests failed on VSTS: simulator tests iOS.

Test results

21 tests failed, 213 tests passed.

Failed tests

  • trimmode link/tvOS - simulator/Release [dotnet]: Failed
  • fsharp/watchOS 32-bits - simulator/Debug: Crashed
  • interdependent-binding-projects/watchOS 32-bits - simulator/Debug: Crashed
  • introspection/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemCoreTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemRuntimeSerializationTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemTransactionsTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemXmlTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemComponentModelDataAnnotationsTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemServiceModelWebTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono MonoCSharpTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono MonoDataSqliteTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemDataTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemDataDataSetExtensionsTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemNetHttpTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemWebServicesTests/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono MicrosoftCSharpXunit/watchOS 32-bits - simulator/Debug: Crashed
  • mscorlib Part 2/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemCoreXunit Part 1/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemCoreXunit Part 2/watchOS 32-bits - simulator/Debug: Crashed

Pipeline on Agent XAMBOT-1043.Monterey'
[perf] Cache Runtime.IsUserType results (#15149)

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ [CI Build] Tests failed on VSTS: simulator tests iOS ❌

Tests failed on VSTS: simulator tests iOS.

Test results

19 tests failed, 215 tests passed.

Failed tests

  • xcframework-test/watchOS 32-bits - simulator/Debug: Crashed
  • introspection/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemCoreTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemTransactionsTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemXmlTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemComponentModelDataAnnotationsTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono MonoDataSqliteTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemDataTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemNetHttpTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono SystemServiceModelTests/watchOS 32-bits - simulator/Debug: Crashed
  • [NUnit] Mono CorlibTests/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemDataXunit/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemNumericsXunit/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono MicrosoftCSharpXunit/watchOS 32-bits - simulator/Debug: Crashed
  • mscorlib Part 1/watchOS 32-bits - simulator/Debug: Crashed
  • mscorlib Part 2/watchOS 32-bits - simulator/Debug: Crashed
  • mscorlib Part 3/watchOS 32-bits - simulator/Debug: Crashed
  • [xUnit] Mono SystemXunit/watchOS 32-bits - simulator/Debug: Crashed

Pipeline on Agent XAMBOT-1107.Monterey'
[perf] Cache Runtime.IsUserType results (#15149)

Please sign in to comment.