Skip to content

Improve the performance of ConditionalWeakTable.TryGetValue - #80059

Merged
jkotas merged 17 commits into
dotnet:mainfrom
AustinWise:austin/TryGetHashCode
Jan 5, 2023
Merged

Improve the performance of ConditionalWeakTable.TryGetValue#80059
jkotas merged 17 commits into
dotnet:mainfrom
AustinWise:austin/TryGetHashCode

Conversation

@AustinWise

@AustinWiseAustinWise commented Dec 30, 2022

Copy link
Copy Markdown
Contributor

Also fixes Objective-C reference tracking in NativeAOT, which was broken by #79519.

Fixes#80032
Related issue: #77472

@ghostghost added area-NativeAOT-coreclr community-contribution Indicates that the PR has been added by a community member labels Dec 30, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #80032

Author:AustinWise
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

Comment threadsrc/coreclr/classlibnative/bcltype/objectnative.cpp Outdated
Comment threadsrc/coreclr/classlibnative/bcltype/objectnative.cpp Outdated
Comment threadsrc/coreclr/classlibnative/bcltype/objectnative.cpp Outdated
Comment threadsrc/tests/Interop/ObjectiveC/ObjectiveCMarshalAPI/Program.cs
@AustinWise
AustinWise marked this pull request as ready for review December 30, 2022 20:37
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@AustinWise

This comment was marked as outdated.

@jkotas

Copy link
Copy Markdown
Member

I would not expect 20% perf regression with the current change. Is it repeatable? Have you looked at the generated code to see what caused it?

@AustinWise

Copy link
Copy Markdown
ContributorAuthor

My bad, the benchmark runner was not using the correct runtime, because I did not pass the args in. I'll correct this and rerun.

@AustinWise

Copy link
Copy Markdown
ContributorAuthor

I updated the benchmark to properly read arguments and try a few different number of objects. It now shows that trying to get a value that is not in the ConditionalWeakTable is a fair amount faster.

usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;namespaceMyBenchmarks{publicclassTryGetHashCode{privatereadonlyList<object>mRootedObjects=new();privatereadonlyConditionalWeakTable<object,object>mWeakTable=new();privateobjectmAnObjectInTheTable=null!;[Params(1,100,1000,10000)]publicintNumberOfObjects;[GlobalSetup]publicvoidSetup(){for(inti=0;i<NumberOfObjects;i++){varobj=newobject();mRootedObjects.Add(obj);mWeakTable.Add(obj,newobject());mAnObjectInTheTable=obj;}}[Benchmark]publicboolTryGetNonExistentValue(){returnmWeakTable.TryGetValue(newobject(),outobject_);}[Benchmark]publicboolTryGetExistingValue(){returnmWeakTable.TryGetValue(mAnObjectInTheTable,outobject_);}}publicclassProgram{publicstaticvoidMain(string[]args){varsummary=BenchmarkRunner.Run<TryGetHashCode>(null!,args);}}}
BenchmarkDotNet=v0.13.3, OS=ubuntu 22.04
AMD Ryzen Threadripper PRO 3955WX 16-Cores, 1 CPU, 32 logical and 16 physical cores
.NET SDK=7.0.101
[Host] : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
Job-FOVPQH : .NET 8.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-WAHSHU : .NET 8.0.0 (42.42.42.42424), X64 RyuJIT AVX2
// * Warnings *
MultimodalDistribution
TryGetHashCode.TryGetExistingValue: Toolchain=merge-base -> It seems that the distribution is bimodal (mValue = 3.29)
MethodJobToolchainNumberOfObjectsMeanErrorStdDevRatioRatioSD
TryGetNonExistentValueJob-FOVPQHmerge-base128.840 ns0.2900 ns0.2712 ns1.000.00
TryGetNonExistentValueJob-WAHSHUPR17.504 ns0.0704 ns0.0624 ns0.260.00
TryGetExistingValueJob-FOVPQHmerge-base112.081 ns0.3200 ns0.9435 ns1.000.00
TryGetExistingValueJob-WAHSHUPR110.224 ns0.2313 ns0.2475 ns0.840.08
TryGetNonExistentValueJob-FOVPQHmerge-base10033.848 ns0.0376 ns0.0333 ns1.000.00
TryGetNonExistentValueJob-WAHSHUPR1007.568 ns0.0164 ns0.0145 ns0.220.00
TryGetExistingValueJob-FOVPQHmerge-base10010.562 ns0.0760 ns0.0711 ns1.000.00
TryGetExistingValueJob-WAHSHUPR10010.089 ns0.1101 ns0.0976 ns0.960.01
TryGetNonExistentValueJob-FOVPQHmerge-base100035.367 ns0.0269 ns0.0224 ns1.000.00
TryGetNonExistentValueJob-WAHSHUPR10007.527 ns0.0183 ns0.0152 ns0.210.00
TryGetExistingValueJob-FOVPQHmerge-base100010.223 ns0.2265 ns0.2945 ns1.000.00
TryGetExistingValueJob-WAHSHUPR10009.775 ns0.1210 ns0.1132 ns0.950.03
TryGetNonExistentValueJob-FOVPQHmerge-base1000035.562 ns0.0861 ns0.0719 ns1.000.00
TryGetNonExistentValueJob-WAHSHUPR100007.795 ns0.1275 ns0.1192 ns0.220.00
TryGetExistingValueJob-FOVPQHmerge-base1000011.644 ns0.2608 ns0.7227 ns1.000.00
TryGetExistingValueJob-WAHSHUPR1000010.384 ns0.0961 ns0.0852 ns0.890.07

AustinWiseand others added 2 commits December 30, 2022 15:13
…erServices/ConditionalWeakTable.cs
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@AustinWise

Copy link
Copy Markdown
ContributorAuthor

I added an implementation for Mono.

One thing to note is that unlike CoreCLR, the hashcodes generated for objects are not deterministic. So the hash table could potentially have a different number of collisions run-to-run. I ran the benchmarks twice and confirmed the ratio between merge-base and PR were roughly the same.

Here is the results for running Mono on x64 Linux. I used these directions to run the benchmarks. I used #80082 to include corerun in the testhost.

BenchmarkDotNet=v0.13.3, OS=ubuntu 22.04
AMD Ryzen Threadripper PRO 3955WX 16-Cores, 1 CPU, 32 logical and 16 physical cores
.NET SDK=7.0.101
[Host] : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
Job-PGRQPW : .NET 8.0.0 (42.42.42.42424) using MonoVM, X64 VectorSize=128
Job-SWPJPT : .NET 8.0.0 (42.42.42.42424) using MonoVM, X64 VectorSize=128
MethodJobToolchainNumberOfObjectsMeanErrorStdDevRatioRatioSD
TryGetNonExistentValueJob-PGRQPWmerge-base155.36 ns0.017 ns0.015 ns1.000.00
TryGetNonExistentValueJob-SWPJPTPR147.41 ns0.027 ns0.024 ns0.860.00
TryGetExistingValueJob-PGRQPWmerge-base151.71 ns0.093 ns0.087 ns1.000.00
TryGetExistingValueJob-SWPJPTPR150.67 ns0.859 ns0.803 ns0.980.02
TryGetNonExistentValueJob-PGRQPWmerge-base10059.69 ns0.078 ns0.073 ns1.000.00
TryGetNonExistentValueJob-SWPJPTPR10050.21 ns0.593 ns0.555 ns0.840.01
TryGetExistingValueJob-PGRQPWmerge-base10054.07 ns0.512 ns0.479 ns1.000.00
TryGetExistingValueJob-SWPJPTPR10049.83 ns0.400 ns0.374 ns0.920.01
TryGetNonExistentValueJob-PGRQPWmerge-base100062.25 ns0.045 ns0.042 ns1.000.00
TryGetNonExistentValueJob-SWPJPTPR100047.91 ns0.059 ns0.055 ns0.770.00
TryGetExistingValueJob-PGRQPWmerge-base100053.12 ns0.131 ns0.116 ns1.000.00
TryGetExistingValueJob-SWPJPTPR100050.17 ns0.480 ns0.449 ns0.940.01
TryGetNonExistentValueJob-PGRQPWmerge-base1000062.63 ns0.244 ns0.229 ns1.000.00
TryGetNonExistentValueJob-SWPJPTPR1000048.70 ns0.133 ns0.124 ns0.780.00
TryGetExistingValueJob-PGRQPWmerge-base1000052.06 ns0.044 ns0.036 ns1.000.00
TryGetExistingValueJob-SWPJPTPR1000051.24 ns0.026 ns0.025 ns0.980.00

I also benchmarked WASM running on V8. I followed these directions to build the runtime. I modfied my benchmark to run against the WASM runtime. See this branch: https://github.com/AustinWise/TryGetHashCodeBenchmark/tree/wasm

BenchmarkDotNet=v0.13.3, OS=ubuntu 22.04
AMD Ryzen Threadripper PRO 3955WX 16-Cores, 1 CPU, 32 logical and 16 physical cores
.NET SDK=7.0.101
[Host] : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
PR : .NET Core (Mono) 8.0.0-dev, Wasm AOT
merge-base : .NET Core (Mono) 8.0.0-dev, Wasm AOT
Runtime=Wasm IterationCount=3 LaunchCount=1 WarmupCount=3 V8 version 11.1.92
MethodJobToolchainNumberOfObjectsMeanErrorStdDevRatioRatioSD
TryGetNonExistentValuePRWasm: PR1235.6 ns23.61 ns1.29 ns0.840.01
TryGetNonExistentValuemerge-baseWasm: merge-base1278.8 ns7.32 ns0.40 ns1.000.00
TryGetExistingValuePRWasm: PR1325.4 ns31.42 ns1.72 ns0.930.04
TryGetExistingValuemerge-baseWasm: merge-base1350.9 ns283.24 ns15.53 ns1.000.00
TryGetNonExistentValuePRWasm: PR100224.6 ns5.16 ns0.28 ns0.760.00
TryGetNonExistentValuemerge-baseWasm: merge-base100294.3 ns16.31 ns0.89 ns1.000.00
TryGetExistingValuePRWasm: PR100329.3 ns15.52 ns0.85 ns0.940.01
TryGetExistingValuemerge-baseWasm: merge-base100349.4 ns47.84 ns2.62 ns1.000.00
TryGetNonExistentValuePRWasm: PR1000254.7 ns40.19 ns2.20 ns0.840.01
TryGetNonExistentValuemerge-baseWasm: merge-base1000303.1 ns12.48 ns0.68 ns1.000.00
TryGetExistingValuePRWasm: PR1000337.1 ns23.65 ns1.30 ns1.020.01
TryGetExistingValuemerge-baseWasm: merge-base1000331.6 ns19.43 ns1.07 ns1.000.00
TryGetNonExistentValuePRWasm: PR10000253.8 ns41.11 ns2.25 ns0.920.01
TryGetNonExistentValuemerge-baseWasm: merge-base10000277.1 ns10.21 ns0.56 ns1.000.00
TryGetExistingValuePRWasm: PR10000367.4 ns21.53 ns1.18 ns1.150.01
TryGetExistingValuemerge-baseWasm: merge-base10000320.2 ns19.91 ns1.09 ns1.000.00

@AustinWiseAustinWise changed the title [NativeAOT] Fix Objective-C reference trackingImproves the performance of ConditionalWeakTable.TryGetValueDec 31, 2022
@AustinWiseAustinWise changed the title Improves the performance of ConditionalWeakTable.TryGetValueImprove the performance of ConditionalWeakTable.TryGetValueDec 31, 2022
@jkotas

Copy link
Copy Markdown
Member

@vargaz@lambdageek Could you please review Mono changes?

@lambdageeklambdageek left a comment

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.

Mono changes LGTM

Comment threadsrc/mono/mono/metadata/object-internals.h Outdated
Comment threadsrc/mono/mono/mini/intrinsics.c Outdated
Comment threadsrc/mono/mono/metadata/monitor.c Outdated
@AustinWise

Copy link
Copy Markdown
ContributorAuthor

With the interpreter transforms actually kicking in, the improvments for Mono WASM are even better. At least I assume that is the reason, I could not figure out how run the interpreter under a debugger.

BenchmarkDotNet=v0.13.3, OS=ubuntu 22.04
AMD Ryzen Threadripper PRO 3955WX 16-Cores, 1 CPU, 32 logical and 16 physical cores
.NET SDK=7.0.101
[Host] : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
PR : .NET Core (Mono) 8.0.0-dev, Wasm AOT
merge-base : .NET Core (Mono) 8.0.0-dev, Wasm AOT
Runtime=Wasm IterationCount=3 LaunchCount=1 WarmupCount=3
V8 version 11.1.92
MethodJobToolchainNumberOfObjectsMeanErrorStdDevRatioRatioSD
TryGetNonExistentValuePRWasm: PR1139.9 ns3.64 ns0.20 ns0.460.00
TryGetNonExistentValuemerge-baseWasm: merge-base1302.9 ns23.78 ns1.30 ns1.000.00
TryGetExistingValuePRWasm: PR1265.1 ns19.45 ns1.07 ns0.810.00
TryGetExistingValuemerge-baseWasm: merge-base1326.2 ns14.47 ns0.79 ns1.000.00
TryGetNonExistentValuePRWasm: PR100136.2 ns8.35 ns0.46 ns0.470.01
TryGetNonExistentValuemerge-baseWasm: merge-base100292.6 ns98.27 ns5.39 ns1.000.00
TryGetExistingValuePRWasm: PR100236.7 ns28.44 ns1.56 ns0.700.03
TryGetExistingValuemerge-baseWasm: merge-base100340.1 ns267.10 ns14.64 ns1.000.00
TryGetNonExistentValuePRWasm: PR1000138.3 ns10.93 ns0.60 ns0.450.00
TryGetNonExistentValuemerge-baseWasm: merge-base1000307.0 ns36.50 ns2.00 ns1.000.00
TryGetExistingValuePRWasm: PR1000233.9 ns22.86 ns1.25 ns0.710.01
TryGetExistingValuemerge-baseWasm: merge-base1000330.4 ns43.60 ns2.39 ns1.000.00
TryGetNonExistentValuePRWasm: PR10000140.2 ns13.44 ns0.74 ns0.430.00
TryGetNonExistentValuemerge-baseWasm: merge-base10000322.6 ns19.63 ns1.08 ns1.000.00
TryGetExistingValuePRWasm: PR10000241.0 ns24.04 ns1.32 ns0.740.01
TryGetExistingValuemerge-baseWasm: merge-base10000327.1 ns51.33 ns2.81 ns1.000.00

@jkotas

Copy link
Copy Markdown
Member

@AustinWise Could you please resolve the merge conflict?

@AustinWise

AustinWise commented Jan 5, 2023

Copy link
Copy Markdown
ContributorAuthor

@jkotas I merged in main. Test failure looks like #74838, but did not dig into this deeply.

@jkotas
jkotas merged commit 5a10aa6 into dotnet:mainJan 5, 2023
@jkotas

Copy link
Copy Markdown
Member

@AustinWise Thank you!

@VSadov

Copy link
Copy Markdown
Member

@AustinWise Thanks for getting this through!!

@AustinWise
AustinWise deleted the austin/TryGetHashCode branch January 6, 2023 04:29
@AustinWise

Copy link
Copy Markdown
ContributorAuthor

Thanks for all the help with reviewing!

Perhaps this PR should be labeled-with tenet-performance so that @stephentoub can find it to potentially include it in the .NET 8 performance improvements post 😀

@jkotasjkotas added the tenet-performance Performance related issue label Jan 6, 2023
@stephentoub

stephentoub commented Jan 6, 2023

Copy link
Copy Markdown
Member

I don't look at the labels. I look at every pr that comes through and track them on a list of prs to consider... this one is already on my list :)

@ghostghost locked as resolved and limited conversation to collaborators Feb 5, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-NativeAOT-coreclrcommunity-contributionIndicates that the PR has been added by a community membertenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NativeAOT + Objective-C Marshal] request for advise on restricted GC callouts

8 participants

@AustinWise@jkotas@VSadov@stephentoub@vargaz@lambdageek@Suchiman@Wraith2