Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

GcHandle Perf Tweaks#9473

Merged
jkotas merged 3 commits into
dotnet:masterfrom
benaadams:gchandle
Feb 10, 2017
Merged

GcHandle Perf Tweaks#9473
jkotas merged 3 commits into
dotnet:masterfrom
benaadams:gchandle

Conversation

@benaadams
Copy link
Copy Markdown
Member

@benaadams benaadams commented Feb 9, 2017

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

Marking GCHandle:SetIsPinned():this as NOINLINE because of unprofitable inline
Successfully inlined IntPtr:op_Explicit(long):long (9 IL bytes) (depth 1) [below ALWAYS_INLINE size]
Successfully inlined IntPtr:.ctor(long):this (9 IL bytes) (depth 1) [below ALWAYS_INLINE size]
**************** Inline Tree
Inlines into 0600369E GCHandle:SetIsPinned():this
  [1 IL=0007 TR=000004 06000C06] [below ALWAYS_INLINE size] IntPtr:op_Explicit(long):long
  [2 IL=0015 TR=000017 06000BF6] [below ALWAYS_INLINE size] IntPtr:.ctor(long):this
Budget: initialTime=138, finalTime=146, initialBudget=1380, currentBudget=1380
Budget: initialSize=724, finalSize=724
; Assembly listing for method GCHandle:SetIsPinned():this
; Emitting BLENDED_CODE for X64 CPU with SSE2
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 this         [V00,T00] (  4,   4  )   byref  ->  rcx         this
;* V01 tmp0         [V01    ] (  0,   0  )    long  ->  zero-ref   
;  V02 tmp1         [V02,T01] (  2,   4  )    long  ->  rax         ld-addr-op
;  V03 tmp2         [V03,T02] (  2,   4  )    long  ->  rax        
;# V04 OutArgs      [V04    ] (  1,   1  )  lclBlk ( 0) [rsp+0x00]  
;
; Lcl frame size = 0

G_M19266_IG01:
       0F1F440000           nop      

G_M19266_IG02:
       488B01               mov      rax, qword ptr [rcx]
       4883C801             or       rax, 1
       488901               mov      qword ptr [rcx], rax

G_M19266_IG03:
       C3                   ret      

; Total bytes of code 16, prolog size 5 for method GCHandle:SetIsPinned():this

// 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);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, agree.

}

return new GCHandle(handle);
internal static object GetTargetFromIntPtr(IntPtr value)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The AssemblyLoadContext is not performance critical. The double verification is not a problem there.

@benaadams
Copy link
Copy Markdown
Member Author

Updated summary

@benaadams
Copy link
Copy Markdown
Member Author

GCHandle:SetIsPinned is probably a bad example as it still doesn't inline :)

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Feb 10, 2017

SetIsPinned

This is how you can get fix it:

            IntPtr handle = InternalAlloc(value, type);

            // Record if the handle is pinned.
            if (type == GCHandleType.Pinned)
            {
#if BIT64
                handle = new IntPtr(((long)handle) | 1L);
#else // !BIT64 (32)
                handle = new IntPtr(((int)handle) | 1);
#endif
            }

            m_handle = handle;

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Feb 10, 2017

Or even better - add this at the top:

#if BIT64
using nint = System.Int64;
#else
using nint = System.Int32;
#endif

And then you can write it as handle = (IntPtr)((nint)handle | 1) without ifdefs.

@benaadams
Copy link
Copy Markdown
Member Author

Yep, that seems to do the trick, also removed GetTargetFromIntPtr

using nuint = System.UInt64;
using nint = System.Int64;
#else
using nuint = System.UInt32;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use signed nint consistently everywhere - since the handle is signed IntPtr?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed

// IMPORTANT: This must be kept in sync with the GCHandleType enum.
private const GCHandleType MaxHandleType = GCHandleType.Pinned;
#if BIT64
private const long NoFlag = ~1L;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can this be just ~(nint)1 without ifdef ? Also, since it is used in just one place, it may not be worth a constant.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Feb 10, 2017

LGTM modulo few nits.

Copy link
Copy Markdown
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

Thanks!

@jkotas jkotas merged commit b85d71f into dotnet:master Feb 10, 2017
@benaadams benaadams deleted the gchandle branch February 10, 2017 13:56
sdmaclea pushed a commit to sdmaclea/coreclr that referenced this pull request Feb 15, 2017
jorive pushed a commit to guhuro/coreclr that referenced this pull request May 4, 2017
* GcHandle Perf Tweaks
@karelz karelz modified the milestone: 2.0.0 Aug 28, 2017
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
* GcHandle Perf Tweaks


Commit migrated from dotnet/coreclr@b85d71f
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants