Skip to content

Optimize and simplify delegate layout#99200

Merged
jkotas merged 102 commits into
dotnet:mainfrom
MichalPetryka:immutable-delegates
Jul 14, 2026
Merged

Optimize and simplify delegate layout#99200
jkotas merged 102 commits into
dotnet:mainfrom
MichalPetryka:immutable-delegates

Conversation

@MichalPetryka

@MichalPetryka MichalPetryka commented Mar 3, 2024

Copy link
Copy Markdown
Contributor

Reduce size of delegate instance by one pointer field

Contributes to #85014.

@ghost ghost added the area-VM-coreclr label Mar 3, 2024
@MichalPetryka

MichalPetryka commented Mar 4, 2024

Copy link
Copy Markdown
Contributor Author

I'm not sure what's up with the failures here, tests that are failing on the CI seem to pass on my machine.
EDIT: I was testing with R2R disabled locally since VS kept complaining about being unable to load PDBs for it.

Comment thread src/coreclr/vm/object.cpp Outdated
MichalPetryka and others added 3 commits March 4, 2024 16:40
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@MichalPetryka
MichalPetryka marked this pull request as ready for review March 4, 2024 23:28
@AndyAyersMS

Copy link
Copy Markdown
Member

/azp run runtime-coreclr gcstress0x3-gcstress0xc

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jkotas

jkotas commented Mar 5, 2024

Copy link
Copy Markdown
Member

Could you please collect some perf numbers to give us an idea about the improvements and regressions in the affected areas? We may want to do some optimizations to mitigate the regressions.

@jkotas

jkotas commented Mar 5, 2024

Copy link
Copy Markdown
Member

One small point of concern is that this might make delegate equality checks slower since they rely on checking the methods in the last "slow path" check, which is however always hit for different delegates AFAIR.

The existing code tries to compare MethodInfos as a cheap fast path. Most delegates do not have cached MethodInfo, so this fast path is hit rarely - but it is very cheap, so it is still worth it.

This cheap fast path is not cheap anymore with this change. It may be best to delete the fast path that is trying to compare the MethodInfos and potentially optimize Delegate_InternalEqualMethodHandles instead.

@MichalPetryka

MichalPetryka commented Mar 5, 2024

Copy link
Copy Markdown
Contributor Author

Could you please collect some perf numbers to give us an idea about the improvements and regressions in the affected areas? We may want to do some optimizations to mitigate the regressions.

I think the thing that'd need benchmarking here are equality checks and maybe the impact of collectible delegates being stored in the CWT on the GC, the rest of things shouldn't be performance sensitive enough to matter I think? I'm not fully sure what'd be the proper way for benchmarking the latter.

This cheap fast path is not cheap anymore with this change. It may be best to delete the fast path that is trying to compare the MethodInfos and potentially optimize Delegate_InternalEqualMethodHandles instead.

I am going to benchmark the impact of the equality change tomorrow, I feel like if the impact won't be big, potential optimizations to it can be done later.

@jkotas

jkotas commented Mar 5, 2024

Copy link
Copy Markdown
Member

I'm not fully sure what'd be the proper way for benchmarking the latter.

Write a small program that loads an assembly as collectible and calls a method in it. The method in collectible assembly can create delegates in a loop. (If you would like to do it under benchmarknet, it works too - but it is probably more work.)

@jkotas

jkotas commented Jul 11, 2026

Copy link
Copy Markdown
Member
/__w/1/s/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs(336,25): error CS0128: A local variable or function named 'method' is already defined in this scope [/__w/1/s/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj]
/__w/1/s/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs(340,20): error CS0266: Cannot implicitly convert type 'System.IRuntimeMethodInfo' to 'System.Reflection.MethodInfo'. An explicit conversion exists (are you missing a cast?) [/__w/1/s/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj]
/__w/1/s/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs(336,25): error IDE0059: Unnecessary assignment of a value to 'method' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0059) [/__w/1/s/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj]

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/UnitTests/ObjectTests.cs:450

  • GetDelegateInfo now has explicit multicast detection via Delegate.HelperObject being an array, but the unit tests no longer exercise that path (the previous "Multicast" test was repurposed to "Unmanaged"). Please add a multicast test that sets HelperObject to an array object and verifies DelegateType.Unknown (and zeroed Target/MethodPtr) for multicast delegates.
    [Theory]
    [ClassData(typeof(MockTarget.StdArch))]
    public void GetDelegateInfo_Unmanaged(MockTarget.Architecture arch)
    {
        const ulong TestMethodTable = 0x00000000_10000200;
        const ulong TestMethodPtr = 0x00000000_aaaa0000;
        const long TestExtraData = -1;
        TargetPointer delegateAddress = default;

        IObject contract = CreateObjectContract(
            arch,
            objectBuilder =>
            {
                delegateAddress = objectBuilder.AddDelegateObject(
                    methodTable: TestMethodTable,
                    target: 0,
                    methodPtr: TestMethodPtr,
                    methodPtrAux: 0,
                    extraData: TestExtraData);
            });

        DelegateInfo info = contract.GetDelegateInfo(delegateAddress);

        Assert.Equal(DelegateType.Unknown, info.DelegateType);
        Assert.Equal(0ul, info.TargetObject.Value);
        Assert.Equal(0ul, info.TargetMethodPtr.Value);
    }

Comment thread src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs
Comment thread src/coreclr/vm/stubmgr.cpp
Comment thread docs/design/datacontracts/Object.md
Comment thread src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs
@jkotas

jkotas commented Jul 12, 2026

Copy link
Copy Markdown
Member

LGTM once the remaining feedback is addressed

@MichalPetryka

Copy link
Copy Markdown
Contributor Author

LGTM once the remaining feedback is addressed

I'll get to it tomorrow

Comment thread src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs Outdated
AaronRobinsonMSFT added a commit that referenced this pull request Jul 14, 2026
Fix Delegate.GetHashCode to return same hashcodes for type-equivalent
delegates pointing to the same method. Delegate.Equals returns true for
such delegates. GetHashCode contract requires hashcode to be the same in
that case.

Found during codereview of #99200

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Michał Petryka <35800402+MichalPetryka@users.noreply.github.com>
Co-authored-by: Aaron R Robinson <arobins@microsoft.com>
@MichalPetryka
MichalPetryka requested a review from jkotas July 14, 2026 06:56
@MichalPetryka

Copy link
Copy Markdown
Contributor Author

@jkotas I think everything should be ready, I decided to go with IsClosed after looking at all the places and it seemed cleaner to me everywhere but I can revert it if you don't like it.

@jkotas

jkotas commented Jul 14, 2026

Copy link
Copy Markdown
Member

/ba-g infrastructure timeouts

@jkotas
jkotas merged commit 3549857 into dotnet:main Jul 14, 2026
179 of 181 checks passed
@jkotas

jkotas commented Jul 14, 2026

Copy link
Copy Markdown
Member

Thank you! There are two follow up changes that I would like to see (two separate PRs):

  • Move everything from MulticastDelegate.CoreCLR.cs to Delegate.CoreCLR.cs
  • Change object[] to Wrapper[] like what it is in NativeAOT to avoid casts and covariance checks
    Do you plan to work on these?

@jkotas jkotas added the tenet-performance Performance related issue label Jul 14, 2026
@jkotas

jkotas commented Jul 14, 2026

Copy link
Copy Markdown
Member

Move everything from MulticastDelegate.CoreCLR.cs to Delegate.CoreCLR.cs

Actually, let me do this one - it is trivial and it is going to fix app compat break #130612

eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fix Delegate.GetHashCode to return same hashcodes for type-equivalent
delegates pointing to the same method. Delegate.Equals returns true for
such delegates. GetHashCode contract requires hashcode to be the same in
that case.

Found during codereview of #99200

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Michał Petryka <35800402+MichalPetryka@users.noreply.github.com>
Co-authored-by: Aaron R Robinson <arobins@microsoft.com>
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-VM-coreclr tenet-performance Performance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants