Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Address Sort<T, TComparer> extensions performance - #39543

Closed
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer
Closed

Address Sort<T, TComparer> extensions performance#39543
nietras wants to merge 13 commits into
dotnet:masterfrom
nietras:sorting-tcomparer

Conversation

@nietras

@nietrasnietras commented Jul 17, 2020

Copy link
Copy Markdown
Contributor

#39466
@jkotas first draft. Please take a look and let me know if this looks like it is on the right path.

TODO:

  • Fix ArraySortHelper.Mono.cs

@Dotnet-GitSync-Bot

Copy link
Copy Markdown
Collaborator

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I'm struggling with getting a good dev loop here, resorting to running:

D:\oss\runtime [sorting-tcomparer ≡]> ./build.cmd -subset Clr.CoreLib

on the command line. And VS won't open the System.Private.CoreLib project in System.Private.CoreLib.sln, any pointers on this? Couldn't find much on CoreLib in docs that helped me.

@jkotas

Copy link
Copy Markdown
Member

VS won't open the System.Private.CoreLib project

It works for me. Do you have the most recent VS update?

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

@nietras

Copy link
Copy Markdown
ContributorAuthor

It works for me. Do you have the most recent VS update?

Version 16.6.4 which should be latest. But I guess I need a preview of preview.8 it seems:

D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj : error : The project file cannot be opened by the project system, because it is missing some critical imports or the referenced SDK cannot be found.
Detailed Information:
Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version.
Project "C:\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (332,3), due to the file not existing.
Project "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\codeOptimization.targets" was not imported by "D:\oss\runtime\src\coreclr\src\System.Private.CoreLib\System.Private.CoreLib.csproj" at (346,3), due to the file not existing.

global.json says 5.0.100-preview.8.20362.3

@nietras

nietras commented Jul 18, 2020

Copy link
Copy Markdown
ContributorAuthor

Will try latest version from https://github.com/dotnet/installer#installers-and-binaries e.g. dotnet-sdk-5.0.100-rc.1.20367.2-win-x64.exe

Seems to work! 👍

…ySortHelper for TKey,TValue scenario, should provide speedup for this.
@jkotas

Copy link
Copy Markdown
Member

I guess I need a preview of preview.8 it seems:

Yes, it is required to make VS work well. We have it mentioned here https://github.com/dotnet/runtime/blob/master/docs/workflow/requirements/windows-requirements.md#net-sdk

{
comparer ??= Comparer<T>.Default;
IntrospectiveSort(keys, comparer.Compare);
if (comparer is null)

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.

This will create two instantiations of the sorting code: One on Comparer<T> and second on TComparer.

Can the null check be pushed out to the callers to avoid the duplication where possible?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@jkotas what if we put !typeof(TComparer).IsValueType && comparer is null?

@jkotasjkotasJul 19, 2020

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.

comparer is null is JITed into a constant already for structs (that are not Nullable<T>). I do not think this would help.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

yeah I thought so, perhaps I don't fully understand your issue here then given only reference type TComparer should be an issue but that should have a canonical instantiation or? would you mind expanding?

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.

For example, when this is called from here:

 https://github.com/dotnet/runtime/pull/39543/files#diff-d4e4a789c4e124d267dde2cf6505da8eR1760

comparer will be null and so we will always take the first branch (as long as this is the only Sort use for the given T). The JIT or AOT won't be able to figure it out. They will create both generic instantiations of the sorting algorithm.

a canonical instantiation

Yes, instantiations over reference types share code, but there is still duplication of the type system structures.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

there is still duplication of the type system structures.

Right, of course :) Just ball-parking here, but would the following then not ensure we only have a single type system structure for reference type TComparer?

if(typeof(TComparer).IsValueType){ComparerArraySortHelper<TKey,TValue,TComparer>.IntrospectiveSort(keys,values,comparer);}else{IComparer<TKey>referenceComparer=comparer??Comparer<TKey>.Default;ComparerArraySortHelper<TKey,TValue,IComparer<TKey>>.IntrospectiveSort(keys,values,referenceComparer);}

@jkotas

Copy link
Copy Markdown
Member

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

@nietras

Copy link
Copy Markdown
ContributorAuthor

Did you have a chance to get some performance numbers? I am curious what the perf is going to look like.

Not yet, wanted to finalize impl first. But on that note do you know which Benchmarks Stephen Toub used and where I can find these? Doesn't look like dotnet/performance. We can start with the latter, though, perhaps.

@stephentoub

Copy link
Copy Markdown
Member

(I used the *sort* tests from dotnet/performance, as well as others like the ones listed in #37941 (comment), and also yours ;-))

@nietras

Copy link
Copy Markdown
ContributorAuthor

@jkotas I can't get:

.\build.cmd -c release

to compile and hence I cannot get a CoreRun.exe for benchmarking. I get errors like:

D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.WindowsRuntimeSystemExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStorageExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.IO.WindowsRuntimeStreamExtensions' does not exist in the implementation but it does exist in the contract.
D:\oss\runtime\src\libraries\shims\ApiCompat.proj(93,5): error : TypesMustExist : Type 'System.Runtime.InteropServices.WindowsRuntime.AsyncInfo' does not exist in the implementation but it does exist in the contract.

I think I have seen this before, but for the life of me can't remember or find what to do? 😅

@nietras

Copy link
Copy Markdown
ContributorAuthor

I can build the same code without my changes fine e.g. 2a1595e but for this PR and branch it fails with above errors and:

error : ApiCompat failed comparing netstandard to netcoreapp

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

just taking notes as I'm trying to find a solution. See https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/updating-ref-source.md this say one could:

dotnet build /p:RunApiCompat=false

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor
./build.cmd -clean
./build.cmd -c Release

🤦‍♂️

Build succeeded.
0 Warning(s)
0 Error(s)

😁

D:\oss\dotnet-performance\src\benchmarks\micro [master ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Array* --statisticalTest 3ms --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

🤞

@nietras

nietras commented Jul 19, 2020

Copy link
Copy Markdown
ContributorAuthor

Well first benchmark run is a bust. Something isn't right. 🤔 runtime-m is master. runtime-pr is this PR.

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-YBDUGJ : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-TJRYUK : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)RatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m5123.806 μs0.8246 μs0.8099 μs3.480 μs3.111 μs5.593 μs1.00Base0.00----
ArrayJob-TJRYUK\runtime-pr5126.625 μs1.2828 μs1.3726 μs6.186 μs3.979 μs9.961 μs1.79Same0.36----
Array_ComparerClassJob-YBDUGJ\runtime-m51218.755 μs0.4069 μs0.4354 μs18.620 μs18.301 μs19.691 μs1.00Base0.00---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51220.257 μs0.1806 μs0.1601 μs20.274 μs20.019 μs20.554 μs1.08Same0.03----
Array_ComparerStructJob-YBDUGJ\runtime-m51222.307 μs0.2758 μs0.2445 μs22.366 μs21.706 μs22.589 μs1.00Base0.00---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51225.287 μs0.4659 μs0.4130 μs25.153 μs24.791 μs26.363 μs1.13Same0.02---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51218.629 μs0.3121 μs0.2767 μs18.590 μs18.329 μs19.224 μs1.00Base0.00----
Array_ComparisonJob-TJRYUK\runtime-pr51218.249 μs0.2300 μs0.1796 μs18.249 μs17.992 μs18.547 μs0.98Same0.01----

Look at the Array test with a massive regression, and we haven't changed that code.

IntClass

This appears more inline with expected. Note that Comparison path in both cases sees no regressions. 👍 The ComparerClass bodes investigation perhaps. ComparerStruct shows allocated bytes, so something is off there too.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioMannWhitney(3ms)Gen 0Gen 1Gen 2Allocated
ArrayJob-YBDUGJ\runtime-m51227.42 μs0.269 μs0.238 μs27.35 μs27.18 μs27.90 μs1.00Base----
ArrayJob-TJRYUK\runtime-pr51227.18 μs0.244 μs0.203 μs27.27 μs26.76 μs27.46 μs0.99Same----
Array_ComparerClassJob-YBDUGJ\runtime-m51236.44 μs0.233 μs0.207 μs36.49 μs36.16 μs36.79 μs1.00Base---64 B
Array_ComparerClassJob-TJRYUK\runtime-pr51240.47 μs0.315 μs0.279 μs40.42 μs40.06 μs41.03 μs1.11Same----
Array_ComparerStructJob-YBDUGJ\runtime-m51241.46 μs0.100 μs0.089 μs41.45 μs41.33 μs41.65 μs1.00Base---88 B
Array_ComparerStructJob-TJRYUK\runtime-pr51241.34 μs0.226 μs0.200 μs41.38 μs40.95 μs41.65 μs1.00Same---24 B
Array_ComparisonJob-YBDUGJ\runtime-m51237.32 μs0.456 μs0.426 μs37.09 μs36.90 μs38.23 μs1.00Base----
Array_ComparisonJob-TJRYUK\runtime-pr51235.77 μs0.153 μs0.136 μs35.73 μs35.61 μs36.06 μs0.96Same----

results.zip

@nietras

Copy link
Copy Markdown
ContributorAuthor

I did a quick benchmark run with ETWProfiler for both m and pr. Looking at int case only, to try to see why this regresses so much even though code hasn't changed. I can't see why from the profiled in perfview. There is no notable difference.

m

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe

image

pr

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --profiler ETW --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

image

Note: Followed https://adamsitnik.com/ETW-Profiler/ for this, since not fresh on my mind. :)

@nietras

Copy link
Copy Markdown
ContributorAuthor

Reran the benchmarks but with CoreRun.exe parameters reversed. This removes the difference between the two for comparable path. Seems to be quite a bit of noise in that measurement.

dotnet run -c Release -f netcoreapp5.0 --filter *.Sort<int*.Array* --coreRun "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"
MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
ArrayJob-WVHVEM\runtime-m\5123.338 μs0.1089 μs0.1254 μs3.332 μs3.161 μs3.539 μs1.020.06----
ArrayJob-TCYRLH\runtime-pr\5123.281 μs0.1022 μs0.1136 μs3.254 μs3.071 μs3.489 μs1.000.00----
Array_ComparerClassJob-WVHVEM\runtime-m\51218.756 μs0.0653 μs0.0545 μs18.757 μs18.661 μs18.891 μs0.910.00---64 B
Array_ComparerClassJob-TCYRLH\runtime-pr\51220.696 μs0.0289 μs0.0241 μs20.696 μs20.655 μs20.728 μs1.000.00----
Array_ComparerStructJob-WVHVEM\runtime-m\51223.301 μs0.1071 μs0.0950 μs23.286 μs23.156 μs23.481 μs0.870.00---88 B
Array_ComparerStructJob-TCYRLH\runtime-pr\51226.943 μs0.0288 μs0.0240 μs26.946 μs26.902 μs26.988 μs1.000.00---24 B
Array_ComparisonJob-WVHVEM\runtime-m\51218.795 μs0.0376 μs0.0314 μs18.787 μs18.758 μs18.849 μs1.000.01----
Array_ComparisonJob-TCYRLH\runtime-pr\51218.815 μs0.2308 μs0.1928 μs18.724 μs18.682 μs19.345 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas if we focus on the int scenario first, there is issues around ComparerClass and ComparerStruct, any comments on that?

The supposed allocations on ComparerStruct I don't get. Nor the benchmark numbers. This should be faster than ComparerClass.

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

Ha had a file length issue causing me not to be able to load etl files in PerfView. Here is the ComparerStruct. Problem is the Compare call isn't getting inlined. There are also a number of methods not getting inlined. This comparer is defined like below in dotnet/performance, which is problematic for reference types, but should work for value types. AggressiveInlining might help... still not sure what the allocs are about... WAIT why is it using canonical ComparerArraySortHelper?? 😅 Clearly I have a code issue somewhere ... I'll try to track it down later.

privatereadonlystructComparableComparerStruct:IComparer<T>{publicintCompare(Tx,Ty)=>x.CompareTo(y);}

image

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas could the issue above be related to how the instance is created via below?

typeof(GenericArraySortHelper<string,Comparer<string>>).TypeHandle

Scratch that, the isssue is in the benchmark using Array.Sort overloads, not span ones. 🤦‍♂️

 [Benchmark]
public void Array_ComparerStruct() => System.Array.Sort(_arrays[_iterationIndex++], 0, Size, new ComparableComparerStruct());

this needs to use span based API of course. Guess should add span based API to benchmarks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

PR Int32

There you go.

MethodSizeMeanErrorStdDevMedianMinMaxGen 0Gen 1Gen 2Allocated
Span5124.982 μs1.7252 μs1.9868 μs3.347 μs3.115 μs7.461 μs----
Span_ComparerClass51220.886 μs0.0362 μs0.0303 μs20.896 μs20.826 μs20.927 μs----
Span_ComparerStruct5124.070 μs0.4887 μs0.5628 μs3.693 μs3.419 μs4.750 μs----
Span_Comparison51219.440 μs0.0417 μs0.0370 μs19.443 μs19.360 μs19.503 μs----

@nietras

Copy link
Copy Markdown
ContributorAuthor

To sum up, running the Span based API micro benchmarks with proper support for TComparer is shown below. I don't think we should pay much notice to the plain Span test as this is too noisy. There is a fundamental issue with how the benchmarks are defined and using the same 5000 invocation count for all tests. I think I will refactor the tests to avoid this and to allow showing some extra perf scenarios.

Anyway, the main issue is the ComparerClass case sees a minor regression. Not sure if this is due the delegate Comparison<T> vs virtual method call perf differences. In micro benchmarks around this I didn't see that big differences though. Any ideas?

Int32

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.388 (2004/?/20H1)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100-rc.1.20367.2
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
Job-SQSCEM : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
Job-GAEOPC : .NET Core 5.0 (CoreCLR 42.42.42.42424, CoreFX 42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable InvocationCount=5000 IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 UnrollFactor=1 WarmupCount=1 
MethodJobSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-SQSCEM\runtime-m5125.149 μs1.1501 μs1.3244 μs6.037 μs3.123 μs6.147 μs1.620.43----
SpanJob-GAEOPC\runtime-pr5123.190 μs0.0665 μs0.0766 μs3.198 μs3.056 μs3.308 μs1.000.00----
Span_ComparerClassJob-SQSCEM\runtime-m51218.779 μs0.0545 μs0.0455 μs18.784 μs18.707 μs18.853 μs0.920.00---64 B
Span_ComparerClassJob-GAEOPC\runtime-pr51220.330 μs0.0654 μs0.0546 μs20.325 μs20.252 μs20.436 μs1.000.00----
Span_ComparerStructJob-SQSCEM\runtime-m51222.569 μs0.0681 μs0.0569 μs22.549 μs22.484 μs22.690 μs5.760.89---88 B
Span_ComparerStructJob-GAEOPC\runtime-pr5123.798 μs0.5000 μs0.5758 μs3.450 μs3.363 μs4.791 μs1.000.00----
Span_ComparisonJob-SQSCEM\runtime-m51218.730 μs0.2270 μs0.1895 μs18.646 μs18.607 μs19.197 μs1.000.01----
Span_ComparisonJob-GAEOPC\runtime-pr51218.748 μs0.2070 μs0.1616 μs18.696 μs18.634 μs19.234 μs1.000.00----

@nietras

nietras commented Jul 20, 2020

Copy link
Copy Markdown
ContributorAuthor

@jkotas latest results via dotnet/performance#1400

results.zip

Two regressions remain:

  • ComparerClass - can be resolved by using ObjectComparisonComparer and still doing the delegate alloc as before. Personally not happy about the alloc but for now focus should be on getting TComparer support.
  • Comparison for BigStruct - this is a little worse and might require JIT changes. Haven't profiled it yet though. A small focused benchmark could perhaps illustrate the problem and an issue could be filed for that.

Int32

Why is the value type comparer scenario faster than simple Span, I have no idea yet.

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
SpanJob-RSHRZO*\runtime-m*5125.531 μs1.1816 μs1.2643 μs6.175 μs3.084 μs6.234 μs1.000.00----
SpanJob-LPTXAZ\runtime-pr\5125.515 μs0.8999 μs1.0363 μs5.951 μs3.103 μs5.984 μs1.000.14----
Span_ComparerClassGenericJob-RSHRZO*\runtime-m*51218.816 μs0.0340 μs0.0318 μs18.813 μs18.748 μs18.868 μs1.000.00---64 B
Span_ComparerClassGenericJob-LPTXAZ\runtime-pr\51220.895 μs0.0191 μs0.0179 μs20.892 μs20.868 μs20.925 μs1.110.00----
Span_ComparerClassSpecificJob-RSHRZO*\runtime-m*51218.747 μs0.0319 μs0.0298 μs18.740 μs18.690 μs18.802 μs1.000.00---64 B
Span_ComparerClassSpecificJob-LPTXAZ\runtime-pr\51220.795 μs0.0306 μs0.0271 μs20.788 μs20.761 μs20.864 μs1.110.00----
Span_ComparerStructGenericJob-RSHRZO*\runtime-m*51223.156 μs0.0439 μs0.0389 μs23.140 μs23.100 μs23.230 μs1.000.00---88 B
Span_ComparerStructGenericJob-LPTXAZ\runtime-pr\5123.468 μs0.0691 μs0.0613 μs3.460 μs3.395 μs3.630 μs0.150.00----
Span_ComparerStructSpecificJob-RSHRZO*\runtime-m*51223.198 μs0.0369 μs0.0345 μs23.191 μs23.134 μs23.275 μs1.000.00---88 B
Span_ComparerStructSpecificJob-LPTXAZ\runtime-pr\5123.510 μs0.0968 μs0.1036 μs3.481 μs3.372 μs3.766 μs0.150.00----
Span_ComparisonJob-RSHRZO*\runtime-m*51218.689 μs0.0456 μs0.0426 μs18.678 μs18.616 μs18.773 μs1.000.00----
Span_ComparisonJob-LPTXAZ\runtime-pr\51218.796 μs0.0413 μs0.0386 μs18.800 μs18.729 μs18.861 μs1.010.00----

BigStruct

MethodJobToolchainSizeMeanErrorStdDevMedianMinMaxRatioGen 0Gen 1Gen 2Allocated
SpanJob-BYCMUO*\runtime-m*5127.949 μs0.0817 μs0.0764 μs7.913 μs7.874 μs8.102 μs1.00----
SpanJob-SZWOVL\runtime-pr\5127.885 μs0.0324 μs0.0271 μs7.892 μs7.827 μs7.923 μs0.99----
Span_ComparerClassGenericJob-BYCMUO*\runtime-m*51226.615 μs0.0404 μs0.0358 μs26.614 μs26.548 μs26.671 μs1.00---64 B
Span_ComparerClassGenericJob-SZWOVL\runtime-pr\51228.723 μs0.0398 μs0.0373 μs28.731 μs28.655 μs28.796 μs1.08----
Span_ComparerClassSpecificJob-BYCMUO*\runtime-m*51226.738 μs0.0658 μs0.0583 μs26.743 μs26.632 μs26.817 μs1.00---64 B
Span_ComparerClassSpecificJob-SZWOVL\runtime-pr\51228.367 μs0.0700 μs0.0655 μs28.361 μs28.259 μs28.515 μs1.06----
Span_ComparerStructGenericJob-BYCMUO*\runtime-m*51230.873 μs0.0998 μs0.0885 μs30.874 μs30.763 μs31.029 μs1.00---88 B
Span_ComparerStructGenericJob-SZWOVL\runtime-pr\51212.050 μs0.0245 μs0.0229 μs12.046 μs12.016 μs12.101 μs0.39----
Span_ComparerStructSpecificJob-BYCMUO*\runtime-m*51231.069 μs0.0912 μs0.0853 μs31.062 μs30.938 μs31.198 μs1.00---88 B
Span_ComparerStructSpecificJob-SZWOVL\runtime-pr\51211.358 μs0.0228 μs0.0213 μs11.362 μs11.318 μs11.387 μs0.37----
Span_ComparisonJob-BYCMUO*\runtime-m*51227.030 μs0.0514 μs0.0481 μs27.017 μs26.963 μs27.112 μs1.00----
Span_ComparisonJob-SZWOVL\runtime-pr\51233.273 μs0.0448 μs0.0397 μs33.278 μs33.186 μs33.341 μs1.23----

Command line

D:\oss\dotnet-performance\src\benchmarks\micro [sort-span ≡]> dotnet run -c Release -f netcoreapp5.0 --filter *.Sort*.Span* --coreRun "D:\oss\runtime-m\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe" "D:\oss\runtime-pr\artifacts\bin\testhost\net5.0-Windows_NT-Release-x64\shared\Microsoft.NETCore.App\5.0.0\CoreRun.exe"

@stephentoub

Copy link
Copy Markdown
Member

@nietras, are you still working on this? Thanks.

@nietras

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I'm waiting for #39732 to be resolved 😅

@stephentoub

Copy link
Copy Markdown
Member

Thanks. @AndyAyersMS, it seems like that's unlikely to be addressed in the foreseeable future?

@AndyAyersMS

Copy link
Copy Markdown
Member

@CarolEidt is this one of the struct issues we've considered as part of .Net 6 planning?

@CarolEidt

Copy link
Copy Markdown
Contributor

is this one of the struct issues we've considered as part of .Net 6 planning?

No, this is more related to optimization of structs, while the struct work that we've planned for .Net 6 (thus far) is focused primarily on completing the work to ensure that structs passed in registers don't needlessly get forced to the stack.

@ViktorHofer

Copy link
Copy Markdown
Member

// Auto-generated message

69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts.

To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others.

@stephentoub

Copy link
Copy Markdown
Member

@AndyAyersMS, recommendations on how to proceed here then?

@AndyAyersMS

Copy link
Copy Markdown
Member

I've pinged @sandreenko on the linked issue to assess for .Net 6. I'm hoping we can implement some forms of struct copy elimination.

@ghostghost closed this Feb 24, 2021
@ghost

Copy link
Copy Markdown

Draft Pull Request was automatically closed for inactivity. It can be manually reopened in the next 30 days if the work resumes.

@ghostghost locked as resolved and limited conversation to collaborators Mar 26, 2021
@stephentoub

Copy link
Copy Markdown
Member

@stephentoub I'm waiting for #39732 to be resolved 😅

@nietras, now that #39732 was addressed, want to have another go at this?

@dotnetdotnet unlocked this conversation Sep 19, 2023
@nietras

Copy link
Copy Markdown
ContributorAuthor

now that #39732 was addressed, want to have another go at this?

@stephentoub hi! Sorry, priorities have shifted since and focusing my spare time on other OSS efforts like https://github.com/nietras/Sep :) I do hope this gets added anyway 🙏

@ghostghost locked as resolved and limited conversation to collaborators Oct 26, 2023
This pull request was closed.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@nietras@Dotnet-GitSync-Bot@jkotas@stephentoub@AndyAyersMS@CarolEidt@ViktorHofer@danmoseley