Improve support for "functor" pattern in .NET, reduce code duplication and help resolve an issue in #39543 which tries to consolidate sorting code on a generic TComparer code path. It would be great if the JIT could be improved in the face of big value types.
Benchmark Code
usingSystem;usingSystem.Collections.Generic;usingSystem.Runtime.CompilerServices;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Diagnosers;usingBenchmarkDotNet.Running;publicclassProgram{staticvoidMain(string[]args)=>BenchmarkSwitcher.FromAssemblies(new[]{typeof(Program).Assembly}).Run(args);[DisassemblyDiagnoser]publicclassCompareBigStruct{Random_random;readonlyComparison<BigStruct>_comparison;readonlyComparison<BigStruct>_comparisonDefaultComparer;readonlyComparer<BigStruct>_defaultComparer;readonlyStructComparer<BigStruct>_structComparer;readonlyStructComparisonComparer<BigStruct>_structComparisonComparer;publicCompareBigStruct(){_defaultComparer=Comparer<BigStruct>.Default;_structComparer=newStructComparer<BigStruct>();_comparison=(x,y)=>x.CompareTo(y);_comparisonDefaultComparer=_defaultComparer.Compare;_structComparisonComparer=newStructComparisonComparer<BigStruct>(_comparison);}publicBigStructX{get;set;}publicBigStructY{get;set;}[GlobalSetup]publicvoidSetup(){_random=newRandom(21317834);X=newBigStruct(_random.Next());Y=newBigStruct(_random.Next());}[Benchmark(Baseline=true)]publicintComparison()=>_comparison(X,Y);[Benchmark()]publicintComparisonDefaultComparer()=>_comparisonDefaultComparer(X,Y);[Benchmark]publicintComparerDefault()=>_defaultComparer.Compare(X,Y);[Benchmark]publicintComparerStruct()=>_structComparer.Compare(X,Y);[Benchmark]publicintComparerStructComparison()=>_structComparisonComparer.Compare(X,Y);}internalreadonlystructStructComparer<T>:IComparer<T>whereT:IComparable<T>{[MethodImpl(MethodImplOptions.AggressiveInlining)]publicintCompare(Tx,Ty)=>x.CompareTo(y);}internalreadonlystructStructComparisonComparer<T>:IComparer<T>{privatereadonlyComparison<T>_comparison;publicStructComparisonComparer(Comparison<T>comparison)=>_comparison=comparison;[MethodImpl(MethodImplOptions.AggressiveInlining)]publicintCompare(Tx,Ty)=>_comparison(x,y);}publicreadonlystructBigStruct:IComparable<BigStruct>{privatereadonlylong_long;privatereadonlyint_int0;privatereadonlyint_int1;privatereadonlyshort_short0;privatereadonlyshort_short1;privatereadonlyshort_short2;privatereadonlyshort_short3;privatereadonlydouble_double;publicBigStruct(intvalue){_long=value;_int0=value;_int1=value;_short0=(short)value;_short1=(short)value;_short2=(short)value;_short3=(short)value;_double=value;}publicintCompareTo(BigStructother)=>_int1.CompareTo(other._int1);}}Benchmark Results
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
DefaultJob : .NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | Code Size |
|---|
| Comparison | 3.500 ns | 0.0065 ns | 0.0051 ns | 1.00 | 144 B |
| ComparisonDefaultComparer | 3.366 ns | 0.0071 ns | 0.0067 ns | 0.96 | 144 B |
| ComparerDefault | 3.661 ns | 0.0059 ns | 0.0049 ns | 1.05 | 160 B |
| ComparerStruct | 2.232 ns | 0.0066 ns | 0.0058 ns | 0.64 | 142 B |
| ComparerStructComparison | 5.269 ns | 0.0071 ns | 0.0060 ns | 1.51 | 219 B |
Benchmark Disassembly
The problem is in the code generated for CompareBigStruct.ComparerStructComparison which can be easily seen.
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
; Program+CompareBigStruct.Comparison()subrsp,0A8vzerouppermovrax,[rcx+10] vmovdqu xmm0,xmmword ptr [rcx+38] vmovdqu xmmword ptr [rsp+88],xmm0 vmovdqu xmm0,xmmword ptr [rcx+48] vmovdqu xmmword ptr [rsp+98],xmm0 vmovdqu xmm0,xmmword ptr [rcx+58] vmovdqu xmmword ptr [rsp+68],xmm0 vmovdqu xmm0,xmmword ptr [rcx+68] vmovdqu xmmword ptr [rsp+78],xmm0 vmovdqu xmm0,xmmword ptr [rsp+88] vmovdqu xmmword ptr [rsp+48],xmm0 vmovdqu xmm0,xmmword ptr [rsp+98] vmovdqu xmmword ptr [rsp+58],xmm0 vmovdqu xmm0,xmmword ptr [rsp+68] vmovdqu xmmword ptr [rsp+28],xmm0 vmovdqu xmm0,xmmword ptr [rsp+78] vmovdqu xmmword ptr [rsp+38],xmm0movrcx,[rax+8]leardx,[rsp+48]lear8,[rsp+28]call qword ptr [rax+18]nopaddrsp,0A8ret; Total bytes of code 144
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
; Program+CompareBigStruct.ComparisonDefaultComparer()subrsp,0A8vzerouppermovrax,[rcx+18] vmovdqu xmm0,xmmword ptr [rcx+38] vmovdqu xmmword ptr [rsp+88],xmm0 vmovdqu xmm0,xmmword ptr [rcx+48] vmovdqu xmmword ptr [rsp+98],xmm0 vmovdqu xmm0,xmmword ptr [rcx+58] vmovdqu xmmword ptr [rsp+68],xmm0 vmovdqu xmm0,xmmword ptr [rcx+68] vmovdqu xmmword ptr [rsp+78],xmm0 vmovdqu xmm0,xmmword ptr [rsp+88] vmovdqu xmmword ptr [rsp+48],xmm0 vmovdqu xmm0,xmmword ptr [rsp+98] vmovdqu xmmword ptr [rsp+58],xmm0 vmovdqu xmm0,xmmword ptr [rsp+68] vmovdqu xmmword ptr [rsp+28],xmm0 vmovdqu xmm0,xmmword ptr [rsp+78] vmovdqu xmmword ptr [rsp+38],xmm0movrcx,[rax+8]leardx,[rsp+48]lear8,[rsp+28]call qword ptr [rax+18]nopaddrsp,0A8ret; Total bytes of code 144
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
; Program+CompareBigStruct.ComparerDefault()subrsp,0A8vzerouppermovrdx,[rcx+20] vmovdqu xmm0,xmmword ptr [rcx+38] vmovdqu xmmword ptr [rsp+88],xmm0 vmovdqu xmm0,xmmword ptr [rcx+48] vmovdqu xmmword ptr [rsp+98],xmm0 vmovdqu xmm0,xmmword ptr [rcx+58] vmovdqu xmmword ptr [rsp+68],xmm0 vmovdqu xmm0,xmmword ptr [rcx+68] vmovdqu xmmword ptr [rsp+78],xmm0 vmovdqu xmm0,xmmword ptr [rsp+88] vmovdqu xmmword ptr [rsp+48],xmm0 vmovdqu xmm0,xmmword ptr [rsp+98] vmovdqu xmmword ptr [rsp+58],xmm0 vmovdqu xmm0,xmmword ptr [rsp+68] vmovdqu xmmword ptr [rsp+28],xmm0 vmovdqu xmm0,xmmword ptr [rsp+78] vmovdqu xmmword ptr [rsp+38],xmm0mov[rsp+20],rdxmovrcx,rdxleardx,[rsp+48]lear8,[rsp+28]movrax,[rsp+20]movrax,[rax]movrax,[rax+40]call qword ptr [rax+20]nopaddrsp,0A8ret; Total bytes of code 160
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
; Program+CompareBigStruct.ComparerStruct()subrsp,88vzeroupper vmovdqu xmm0,xmmword ptr [rcx+38] vmovdqu xmmword ptr [rsp+68],xmm0 vmovdqu xmm0,xmmword ptr [rcx+48] vmovdqu xmmword ptr [rsp+78],xmm0 vmovdqu xmm0,xmmword ptr [rcx+58] vmovdqu xmmword ptr [rsp+48],xmm0 vmovdqu xmm0,xmmword ptr [rcx+68] vmovdqu xmmword ptr [rsp+58],xmm0 vmovdqu xmm0,xmmword ptr [rsp+68] vmovdqu xmmword ptr [rsp+28],xmm0 vmovdqu xmm0,xmmword ptr [rsp+78] vmovdqu xmmword ptr [rsp+38],xmm0 vmovdqu xmm0,xmmword ptr [rsp+48] vmovdqu xmmword ptr [rsp+8],xmm0 vmovdqu xmm0,xmmword ptr [rsp+58] vmovdqu xmmword ptr [rsp+18],xmm0moveax,[rsp+14]cmp[rsp+34],eaxjge short M00_L00moveax,0FFFFFFFFjmp short M00_L02M00_L00:cmp[rsp+34],eaxjle short M00_L01moveax,1jmp short M00_L02M00_L01:xoreax,eaxM00_L02:addrsp,88ret; Total bytes of code 142
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
; Program+CompareBigStruct.ComparerStructComparison()subrsp,0E8vzeroupperleardx,[rcx+30] vmovdqu xmm0,xmmword ptr [rcx+38] vmovdqu xmmword ptr [rsp+0C8],xmm0 vmovdqu xmm0,xmmword ptr [rcx+48] vmovdqu xmmword ptr [rsp+0D8],xmm0 vmovdqu xmm0,xmmword ptr [rcx+58] vmovdqu xmmword ptr [rsp+0A8],xmm0 vmovdqu xmm0,xmmword ptr [rcx+68] vmovdqu xmmword ptr [rsp+0B8],xmm0 vmovdqu xmm0,xmmword ptr [rsp+0C8] vmovdqu xmmword ptr [rsp+88],xmm0 vmovdqu xmm0,xmmword ptr [rsp+0D8] vmovdqu xmmword ptr [rsp+98],xmm0 vmovdqu xmm0,xmmword ptr [rsp+0A8] vmovdqu xmmword ptr [rsp+68],xmm0 vmovdqu xmm0,xmmword ptr [rsp+0B8] vmovdqu xmmword ptr [rsp+78],xmm0movrax,[rdx] vmovdqu xmm0,xmmword ptr [rsp+88] vmovdqu xmmword ptr [rsp+48],xmm0 vmovdqu xmm0,xmmword ptr [rsp+98] vmovdqu xmmword ptr [rsp+58],xmm0 vmovdqu xmm0,xmmword ptr [rsp+68] vmovdqu xmmword ptr [rsp+28],xmm0 vmovdqu xmm0,xmmword ptr [rsp+78] vmovdqu xmmword ptr [rsp+38],xmm0movrcx,[rax+8]leardx,[rsp+48]lear8,[rsp+28]call qword ptr [rax+18]nopaddrsp,0E8ret; Total bytes of code 219
cc: @jkotas @dotnet/jit-contrib
category:cq
theme:structs
skill-level:expert
cost:large
impact:medium
Improve support for "functor" pattern in .NET, reduce code duplication and help resolve an issue in #39543 which tries to consolidate sorting code on a generic
TComparercode path. It would be great if the JIT could be improved in the face of big value types.Benchmark Code
Benchmark Results
Benchmark Disassembly
The problem is in the code generated for
CompareBigStruct.ComparerStructComparisonwhich can be easily seen..NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
.NET Core 5.0.0 (CoreCLR 5.0.20.36102, CoreFX 5.0.20.36102), X64 RyuJIT
cc: @jkotas @dotnet/jit-contrib
category:cq
theme:structs
skill-level:expert
cost:large
impact:medium