GcHandle Perf Tweaks#9473
Conversation
| // Free the handle if it hasn't already been freed. | ||
| if (handle != IntPtr.Zero && Interlocked.CompareExchange(ref m_handle, IntPtr.Zero, handle) == handle) | ||
| { | ||
| IntPtr handle = Interlocked.Exchange(ref m_handle, IntPtr.Zero); |
There was a problem hiding this comment.
Doesn't need CompareExchange as m_handle always ends up as IntPtr.Zero; so can just Exchange it with IntPtr.Zero and the perform the tests on the result
| } | ||
|
|
||
| return new GCHandle(handle); | ||
| internal static object GetTargetFromIntPtr(IntPtr value) |
There was a problem hiding this comment.
AssemblyLoadContext does a set of handle double verifies with the target property: GCHandle.FromIntPtr(...).Target as seemed to be a particular area where some of the functions where getting marked as no-inline. So I collapsed the two calls and gave its own internal function.
However, I think the most significant change was m_handle == IntPtr.Zero to m_handle.IsNull() so I could probably revert this and retest?
There was a problem hiding this comment.
The AssemblyLoadContext is not performance critical. The double verification is not a problem there.
|
Updated summary |
|
|
This is how you can get fix it: |
|
Or even better - add this at the top: And then you can write it as |
| using nuint = System.UInt64; | ||
| using nint = System.Int64; | ||
| #else | ||
| using nuint = System.UInt32; |
There was a problem hiding this comment.
Can we use signed nint consistently everywhere - since the handle is signed IntPtr?
| // IMPORTANT: This must be kept in sync with the GCHandleType enum. | ||
| private const GCHandleType MaxHandleType = GCHandleType.Pinned; | ||
| #if BIT64 | ||
| private const long NoFlag = ~1L; |
There was a problem hiding this comment.
Can this be just ~(nint)1 without ifdef ? Also, since it is used in just one place, it may not be worth a constant.
|
LGTM modulo few nits. |
* GcHandle Perf Tweaks
* GcHandle Perf Tweaks
* GcHandle Perf Tweaks Commit migrated from dotnet/coreclr@b85d71f

GCHandle is either mostly a thin wrapper around VM functions; or simple bitshifts and casts. However its current implementation has significant overhead as it is called a huge amount and its functions are marked by NGen as permanently uninlinable.
Previously 2,028.497 GCHandle calls for 158,475 libuv write calls (in Marshal)
After 814 GCHandle calls for 2,760,965 libuv write calls (in Marshal)
Mostly they are fairly simple, so for example while GCHandle:SetIsPinned is unprofitable and permanently no inline; is asm ends up extremely simple