Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[perf] Cache Runtime.IsUserType results #15149

Merged
merged 4 commits into from
Jun 1, 2022

Conversation

spouliot
Copy link
Contributor

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

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 xamarin#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)
@dalexsoto
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@dalexsoto dalexsoto added community Community contribution ❤ notes-mention Deserves a mention in release notes labels May 29, 2022
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

src/ObjCRuntime/Runtime.cs Outdated Show resolved Hide resolved
internal static bool IsUserType (IntPtr self)
{
var cls = Class.object_getClass (self);
if (!usertype_cache.TryGetValue (cls, out var result)) {
Copy link
Member

Choose a reason for hiding this comment

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

This would need a lock to protect against race conditions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would a ConditionalWeakTable work here instead? I imagine class objects are bound to application lifetime.

Copy link
Member

Choose a reason for hiding this comment

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

Both the key and the value are value types, and I believe ConditionalWeakTable only works with reference types.

Also the key represents the native Class object, and that will never be freed, so I don't see much value in trying to keep track of lifetimes on the managed side.

@rolfbjarne
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne
Copy link
Member

rolfbjarne commented May 31, 2022

Looks like that ran into trouble:

MonoTouchFixtures.ObjCRuntime.RuntimeTest.ResurrectedObjectsDisposedTest
	[PASS] ResurrectedObjectsDisposedTest(Foundation.NSObject)
	[FAIL] ResurrectedObjectsDisposedTest(MonoTouchFixtures.ObjCRuntime.RuntimeTest+ResurrectedObjectsDisposedTestClass) :   broken count
  Expected: 0
  But was:  9

		   at MonoTouchFixtures.ObjCRuntime.RuntimeTest.ResurrectedObjectsDisposedTest(Type type) in /Users/builder/azdo/_work/1/s/xamarin-macios/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs:line 604
MonoTouchFixtures.ObjCRuntime.RuntimeTest.ResurrectedObjectsDisposedTest : 1387.3894 ms
	[PASS] ToggleRef_NonToggledObjectsShouldBeCollected
	[PASS] ToggleRef_ToggledObjectsShouldNotBeCollected
2022-05-30 13:24:38.455 monotouchtest[35844:8735022] Microsoft.macOS: Internal consistency error, please file a bug (https://github.com/xamarin/xamarin-macios/issues/new). Additional data: found managed object 0x60000f8a0300=0x0 (MonoTouchFixtures.ObjCRuntime.RuntimeTest+ReleaseNotifier) in native object 0x60000f8a02c0 (MonoTouchFixtures_ObjCRuntime_RuntimeTest_ReleaseNotifier).
Process monotouchtest exited with 134

Seems to happen when running monotouch-test on .NET (but not legacy Xamarin for some reason).

internal static bool IsUserType (IntPtr self)
{
var cls = Class.object_getClass (self);
lock (usertype_cache) {
#if NET
var result = CollectionsMarshal.GetValueRefOrAddDefault (usertype_cache, cls, out var exists);
Copy link
Member

@rolfbjarne rolfbjarne May 31, 2022

Choose a reason for hiding this comment

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

I think this is needed to fix the test failures:

Suggested change
var result = CollectionsMarshal.GetValueRefOrAddDefault (usertype_cache, cls, out var exists);
ref var result = ref CollectionsMarshal.GetValueRefOrAddDefault (usertype_cache, cls, out var exists);

@rolfbjarne
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@vs-mobiletools-engineering-service2
Copy link
Collaborator

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

All tests on macOS Mac Catalina (10.15) passed.

Pipeline on Agent
Hash: 903219af732bf74a056a94f4b6ac657e27a24cb1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

📚 [PR Build] Artifacts 📚

Packages generated

View packages

Pipeline on Agent XAMBOT-1165.Monterey
Hash: 903219af732bf74a056a94f4b6ac657e27a24cb1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

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

All tests on macOS Mac Catalina (10.15) passed.

Pipeline on Agent
Hash: 903219af732bf74a056a94f4b6ac657e27a24cb1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

📋 [PR 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 XAMBOT-1164.Monterey'
Hash: 903219af732bf74a056a94f4b6ac657e27a24cb1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [PR 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: 903219af732bf74a056a94f4b6ac657e27a24cb1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

📋 [PR 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 XAMBOT-1166.Monterey
Hash: 903219af732bf74a056a94f4b6ac657e27a24cb1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

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

Failed tests are:

  • xammac_tests
  • monotouch-test

Pipeline on Agent
Hash: 903219af732bf74a056a94f4b6ac657e27a24cb1

@vs-mobiletools-engineering-service2
Copy link
Collaborator

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

Tests failed on VSTS: simulator tests iOS.

Test results

1 tests failed, 147 tests passed.

Failed tests

  • mono-native-compat/watchOS 32-bits - simulator/Debug: Crashed

Pipeline on Agent XAMBOT-1097.Monterey'
Merge 903219a into c17fd25

@vs-mobiletools-engineering-service2
Copy link
Collaborator

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

Tests failed on VSTS: simulator tests iOS.

Test results

3 tests failed, 145 tests passed.

Failed tests

  • monotouch-test/tvOS - simulator/Debug [dotnet]: Failed
  • monotouch-test/watchOS 32-bits - simulator/Release (all optimizations): TimedOut
  • fsharp/watchOS 32-bits - simulator/Debug: Crashed

Pipeline on Agent XAMBOT-1108.Monterey'
Merge 903219a into c17fd25

@rolfbjarne
Copy link
Member

Test failures are unrelated:

@rolfbjarne rolfbjarne merged commit 33b73a1 into xamarin:main Jun 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community Community contribution ❤ notes-mention Deserves a mention in release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants