Skip to content

Inline ValueOption module - #15709

Merged
vzarytovskii merged 3 commits into
dotnet:mainfrom
kerams:vo
Jul 31, 2023
Merged

Inline ValueOption module#15709
vzarytovskii merged 3 commits into
dotnet:mainfrom
kerams:vo

Conversation

@kerams

Copy link
Copy Markdown
Contributor

Continuing with #14927 and https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1115-InlineIfLambda-in-FSharp-Core.md.

Benchmark 1

Code

openBenchmarkDotNet.AttributesopenBenchmarkDotNet.ConfigsmoduleInline =let inlinedefaultWith defThunk option =match option with| ValueNone -> defThunk ()| ValueSome v -> v
let inlinemap mapping option =match option with| ValueNone -> ValueNone
| ValueSome x -> ValueSome (mapping x)moduleInlineAndLambda =let inlinedefaultWith([<InlineIfLambda>]defThunk)option =match option with| ValueNone -> defThunk ()| ValueSome v -> v
let inlinemap([<InlineIfLambda>]mapping)option =match option with| ValueNone -> ValueNone
| ValueSome x -> ValueSome (mapping x)// Some function with a bunch of instructions that isn't going to cause lambda inlining without InlineIfLambdalet inliney()=if1/1=1then100/100else2/3[<MemoryDiagnoser>]typeCurrent()=lets= ValueSome System.DateTime.Now.Day
letn= ValueOption<int>.None
[<NoCompilerInlining>]letf=10[<Benchmark>]member_.DefaultWithSingletonNone()=
ValueOption.defaultWith (fun()->41+ y ()) n
[<Benchmark>]member_.DefaultWithNone()=
ValueOption.defaultWith (fun()->42+ f + y ()) n
[<Benchmark>]member_.MapSingletonSome()=
ValueOption.map (fun x -> x +43+ y ()) s
[<Benchmark>]member_.MapSingletonNone()=
ValueOption.map (fun x -> x +44+ y ()) n
[<Benchmark>]member_.MapSome()=
ValueOption.map (fun x -> x + f + y ()) s
[<Benchmark>]member_.MapNone()=
ValueOption.map (fun x -> x + f + y ()) n
[<MemoryDiagnoser>]typeInline()=lets= ValueSome System.DateTime.Now.Day
letn= ValueOption<int>.None
[<NoCompilerInlining>]letf=10[<Benchmark>]member_.DefaultWithSingletonNone()=
Inline.defaultWith (fun()->41+ y ()) n
[<Benchmark>]member_.DefaultWithNone()=
Inline.defaultWith (fun()->42+ f + y ()) n
[<Benchmark>]member_.MapSingletonSome()=
Inline.map (fun x -> x +43+ y ()) s
[<Benchmark>]member_.MapSingletonNone()=
Inline.map (fun x -> x +44+ y ()) n
[<Benchmark>]member_.MapSome()=
Inline.map (fun x -> x + f + y ()) s
[<Benchmark>]member_.MapNone()=
Inline.map (fun x -> x + f + y ()) n
[<MemoryDiagnoser>]typeInlineAndLambda()=lets= ValueSome System.DateTime.Now.Day
letn= ValueOption<int>.None
[<NoCompilerInlining>]letf=10[<Benchmark>]member_.DefaultWithSingletonNone()=
InlineAndLambda.defaultWith (fun()->41+ y ()) n
[<Benchmark>]member_.DefaultWithNone()=
InlineAndLambda.defaultWith (fun()->42+ f + y ()) n
[<Benchmark>]member_.MapSingletonSome()=
InlineAndLambda.map (fun x -> x +43+ y ()) s
[<Benchmark>]member_.MapSingletonNone()=
InlineAndLambda.map (fun x -> x +44+ y ()) n
[<Benchmark>]member_.MapSome()=
InlineAndLambda.map (fun x -> x + f + y ()) s
[<Benchmark>]member_.MapNone()=
InlineAndLambda.map (fun x -> x + f + y ()) n
BenchmarkDotNet.Running.BenchmarkRunner.Run (
typeof<Current>.Assembly,
DefaultConfig.Instance.WithOption (ConfigOptions.JoinSummary,true))|> ignore

Decompiled

usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;using<StartupCode$Bench>;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Reports;usingMicrosoft.FSharp.Core;[CompilationMapping(SourceConstructFlags.Module)]publicstaticclassProgram{publicstaticinty(){if(1/1==1){return100/100;}return2/3;}[CompilerGenerated]internalstaticintdefThunk@5(UnitunitVar0){return41+((1/1!=1)?(2/3):(100/100));}[CompilerGenerated]internalstaticintdefThunk@5-1(Program.Inline_,UnitunitVar0){return42+_.f+((1/1!=1)?(2/3):(100/100));}[CompilerGenerated]internalstaticintmapping@10(intx){returnx+43+((1/1!=1)?(2/3):(100/100));}[CompilerGenerated]internalstaticintmapping@10-1(intx){returnx+44+((1/1!=1)?(2/3):(100/100));}[CompilerGenerated]internalstaticintmapping@10-2(Program.Inline_,intx){returnx+_.f+((1/1!=1)?(2/3):(100/100));}[CompilerGenerated]internalstaticintmapping@10-3(Program.Inline_,intx){returnx+_.f+((1/1!=1)?(2/3):(100/100));}[CompilationMapping(SourceConstructFlags.Value)]internalstaticSummary[]arg@1{get{return $Program.arg@1;}}[MemoryDiagnoser(true)][CompilationMapping(SourceConstructFlags.ObjectType)][Serializable]publicclassCurrent{publicCurrent():this(){this.s=FSharpValueOption<int>.NewValueSome(DateTime.Now.Day);this.n=FSharpValueOption<int>.ValueNone;this.f=10;}[Benchmark(43,"C:\\code\\Test\\Bench\\Program.fs")]publicintDefaultWithSingletonNone(){returnValueOption.DefaultWith<int>(Program.DefaultWithSingletonNone@45.@_instance,this.n);}[Benchmark(47,"C:\\code\\Test\\Bench\\Program.fs")]publicintDefaultWithNone(){returnValueOption.DefaultWith<int>(newProgram.DefaultWithNone@49(this),this.n);}[Benchmark(51,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSingletonSome(){returnValueOption.Map<int,int>(Program.MapSingletonSome@53.@_instance,this.s);}[Benchmark(55,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSingletonNone(){returnValueOption.Map<int,int>(Program.MapSingletonNone@57.@_instance,this.n);}[Benchmark(59,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSome(){returnValueOption.Map<int,int>(newProgram.MapSome@61(this),this.s);}[Benchmark(63,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapNone(){returnValueOption.Map<int,int>(newProgram.MapNone@65(this),this.n);}internalFSharpValueOption<int>s;internalFSharpValueOption<int>n;[NoCompilerInlining]internalintf;}[Serializable]internalsealedclassDefaultWithSingletonNone@45:FSharpFunc<Unit,int>{[CompilerGenerated][DebuggerNonUserCode]internalDefaultWithSingletonNone@45(){}publicoverrideintInvoke(UnitunitVar0){return41+((1/1!=1)?(2/3):(100/100));}// Note: this type is marked as 'beforefieldinit'.staticDefaultWithSingletonNone@45(){}internalstaticreadonlyProgram.DefaultWithSingletonNone@45@_instance=newProgram.DefaultWithSingletonNone@45();}[Serializable]internalsealedclassDefaultWithNone@49:FSharpFunc<Unit,int>{[CompilerGenerated][DebuggerNonUserCode]internalDefaultWithNone@49(Program.Current_){this._=_;}publicoverrideintInvoke(UnitunitVar0){return42+this._.f+((1/1!=1)?(2/3):(100/100));}publicProgram.Current_;}[Serializable]internalsealedclassMapSingletonSome@53:FSharpFunc<int,int>{[CompilerGenerated][DebuggerNonUserCode]internalMapSingletonSome@53(){}publicoverrideintInvoke(intx){returnx+43+((1/1!=1)?(2/3):(100/100));}// Note: this type is marked as 'beforefieldinit'.staticMapSingletonSome@53(){}internalstaticreadonlyProgram.MapSingletonSome@53@_instance=newProgram.MapSingletonSome@53();}[Serializable]internalsealedclassMapSingletonNone@57:FSharpFunc<int,int>{[CompilerGenerated][DebuggerNonUserCode]internalMapSingletonNone@57(){}publicoverrideintInvoke(intx){returnx+44+((1/1!=1)?(2/3):(100/100));}// Note: this type is marked as 'beforefieldinit'.staticMapSingletonNone@57(){}internalstaticreadonlyProgram.MapSingletonNone@57@_instance=newProgram.MapSingletonNone@57();}[Serializable]internalsealedclassMapSome@61:FSharpFunc<int,int>{[CompilerGenerated][DebuggerNonUserCode]internalMapSome@61(Program.Current_){this._=_;}publicoverrideintInvoke(intx){returnx+this._.f+((1/1!=1)?(2/3):(100/100));}publicProgram.Current_;}[Serializable]internalsealedclassMapNone@65:FSharpFunc<int,int>{[CompilerGenerated][DebuggerNonUserCode]internalMapNone@65(Program.Current_){this._=_;}publicoverrideintInvoke(intx){returnx+this._.f+((1/1!=1)?(2/3):(100/100));}publicProgram.Current_;}[MemoryDiagnoser(true)][CompilationMapping(SourceConstructFlags.ObjectType)][Serializable]publicclassInline{publicInline():this(){this.s=FSharpValueOption<int>.NewValueSome(DateTime.Now.Day);this.n=FSharpValueOption<int>.ValueNone;this.f=10;}[Benchmark(77,"C:\\code\\Test\\Bench\\Program.fs")]publicintDefaultWithSingletonNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}returnProgram.defThunk@5(null);}[Benchmark(81,"C:\\code\\Test\\Bench\\Program.fs")]publicintDefaultWithNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}returnProgram.defThunk@5-1(this,null);}[Benchmark(85,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSingletonSome(){FSharpValueOption<int>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){intitem=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(Program.mapping@10(item));}returnFSharpValueOption<int>.ValueNone;}[Benchmark(89,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSingletonNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){intitem=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(Program.mapping@10-1(item));}returnFSharpValueOption<int>.ValueNone;}[Benchmark(93,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSome(){FSharpValueOption<int>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){intitem=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(Program.mapping@10-2(this,item));}returnFSharpValueOption<int>.ValueNone;}[Benchmark(97,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){intitem=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(Program.mapping@10-3(this,item));}returnFSharpValueOption<int>.ValueNone;}internalFSharpValueOption<int>s;internalFSharpValueOption<int>n;[NoCompilerInlining]internalintf;}[MemoryDiagnoser(true)][CompilationMapping(SourceConstructFlags.ObjectType)][Serializable]publicclassInlineAndLambda{publicInlineAndLambda():this(){this.s=FSharpValueOption<int>.NewValueSome(DateTime.Now.Day);this.n=FSharpValueOption<int>.ValueNone;this.f=10;}[Benchmark(111,"C:\\code\\Test\\Bench\\Program.fs")]publicintDefaultWithSingletonNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}return41+((1/1!=1)?(2/3):(100/100));}[Benchmark(115,"C:\\code\\Test\\Bench\\Program.fs")]publicintDefaultWithNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}return42+this.f+((1/1!=1)?(2/3):(100/100));}[Benchmark(119,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSingletonSome(){FSharpValueOption<int>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){intx=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(x+43+((1/1!=1)?(2/3):(100/100)));}returnFSharpValueOption<int>.ValueNone;}[Benchmark(123,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSingletonNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){intx=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(x+44+((1/1!=1)?(2/3):(100/100)));}returnFSharpValueOption<int>.ValueNone;}[Benchmark(127,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapSome(){FSharpValueOption<int>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){intx=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(x+this.f+((1/1!=1)?(2/3):(100/100)));}returnFSharpValueOption<int>.ValueNone;}[Benchmark(131,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<int>MapNone(){FSharpValueOption<int>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){intx=fsharpValueOption.Item;returnFSharpValueOption<int>.NewValueSome(x+this.f+((1/1!=1)?(2/3):(100/100)));}returnFSharpValueOption<int>.ValueNone;}internalFSharpValueOption<int>s;internalFSharpValueOption<int>n;[NoCompilerInlining]internalintf;}[CompilationMapping(SourceConstructFlags.Module)]publicstaticclassInlineAndLambdaModule{[CompilationArgumentCounts(newint[]{1,1})]publicstaticadefaultWith<a>([InlineIfLambda]FSharpFunc<Unit,a>defThunk,FSharpValueOption<a>option){if(option.Tag==1){returnoption.Item;}returndefThunk.Invoke(null);}[CompilationArgumentCounts(newint[]{1,1})]publicstaticFSharpValueOption<b>map<a,b>([InlineIfLambda]FSharpFunc<a,b>mapping,FSharpValueOption<a>option){if(option.Tag==1){ax=option.Item;returnFSharpValueOption<b>.NewValueSome(mapping.Invoke(x));}returnFSharpValueOption<b>.ValueNone;}}[CompilationMapping(SourceConstructFlags.Module)]publicstaticclassInlineModule{[CompilationArgumentCounts(newint[]{1,1})]publicstaticadefaultWith<a>(FSharpFunc<Unit,a>defThunk,FSharpValueOption<a>option){if(option.Tag==1){returnoption.Item;}returndefThunk.Invoke(null);}[CompilationArgumentCounts(newint[]{1,1})]publicstaticFSharpValueOption<b>map<a,b>(FSharpFunc<a,b>mapping,FSharpValueOption<a>option){if(option.Tag==1){ax=option.Item;returnFSharpValueOption<b>.NewValueSome(mapping.Invoke(x));}returnFSharpValueOption<b>.ValueNone;}}}

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1992/22H2/2022Update/SunValley2)
AMD Ryzen 9 7900, 1 CPU, 24 logical and 12 physical cores
.NET SDK=8.0.100-preview.6.23330.14
[Host] : .NET 8.0.0 (8.0.23.32907), X64 RyuJIT AVX2 DEBUG
DefaultJob : .NET 8.0.0 (8.0.23.32907), X64 RyuJIT AVX2
TypeMethodMeanErrorStdDevGen0Allocated
CurrentDefaultWithSingletonNone0.6079 ns0.0107 ns0.0100 ns--
InlineDefaultWithSingletonNone0.0060 ns0.0014 ns0.0013 ns--
InlineAndLambdaDefaultWithSingletonNone0.1994 ns0.0096 ns0.0090 ns--
CurrentDefaultWithNone3.3048 ns0.0588 ns0.0550 ns0.001424 B
InlineDefaultWithNone0.1957 ns0.0078 ns0.0069 ns--
InlineAndLambdaDefaultWithNone0.1968 ns0.0097 ns0.0090 ns--
CurrentMapSingletonSome3.2679 ns0.0240 ns0.0212 ns--
InlineMapSingletonSome2.8726 ns0.0082 ns0.0076 ns--
InlineAndLambdaMapSingletonSome2.8581 ns0.0035 ns0.0028 ns--
CurrentMapSingletonNone0.5504 ns0.0037 ns0.0033 ns--
InlineMapSingletonNone0.2003 ns0.0063 ns0.0059 ns--
InlineAndLambdaMapSingletonNone0.2014 ns0.0031 ns0.0028 ns--
CurrentMapSome4.6083 ns0.0375 ns0.0350 ns0.001424 B
InlineMapSome3.0991 ns0.0823 ns0.0979 ns--
InlineAndLambdaMapSome3.0532 ns0.0084 ns0.0075 ns--
CurrentMapNone3.0646 ns0.0280 ns0.0262 ns0.001424 B
InlineMapNone0.2004 ns0.0015 ns0.0012 ns--
InlineAndLambdaMapNone0.2042 ns0.0032 ns0.0026 ns--

Benchmark 2

Code

openBenchmarkDotNet.AttributesopenBenchmarkDotNet.ConfigsmoduleInline =let inlinedefaultWith defThunk option =match option with| ValueNone -> defThunk ()| ValueSome v -> v
let inlinemap mapping option =match option with| ValueNone -> ValueNone
| ValueSome x -> ValueSome (mapping x)moduleInlineAndLambda =let inlinedefaultWith([<InlineIfLambda>]defThunk)option =match option with| ValueNone -> defThunk ()| ValueSome v -> v
let inlinemap([<InlineIfLambda>]mapping)option =match option with| ValueNone -> ValueNone
| ValueSome x -> ValueSome (mapping x)// Some function with a bunch of instructions that isn't going to cause lambda inlining without InlineIfLambdalet inliney()=if1/1=1then100L /100L
else2L /3L
[<MemoryDiagnoser>]typeCurrent()=lets= ValueSome (int64 System.DateTime.Now.Day)letn= ValueOption<int64>.None
[<NoCompilerInlining>]letf=10L
[<Benchmark>]member_.DefaultWithSingletonNone()=
ValueOption.defaultWith (fun()->41L + y ()) n
[<Benchmark>]member_.DefaultWithNone()=
ValueOption.defaultWith (fun()->42L + f + y ()) n
[<Benchmark>]member_.MapSingletonSome()=
ValueOption.map (fun x -> x +43L + y ()) s
[<Benchmark>]member_.MapSingletonNone()=
ValueOption.map (fun x -> x +44L + y ()) n
[<Benchmark>]member_.MapSome()=
ValueOption.map (fun x -> x + f + y ()) s
[<Benchmark>]member_.MapNone()=
ValueOption.map (fun x -> x + f + y ()) n
[<MemoryDiagnoser>]typeInline()=lets= ValueSome (int64 System.DateTime.Now.Day)letn= ValueOption<int64>.None
[<NoCompilerInlining>]letf=10L
[<Benchmark>]member_.DefaultWithSingletonNone()=
Inline.defaultWith (fun()->41L + y ()) n
[<Benchmark>]member_.DefaultWithNone()=
Inline.defaultWith (fun()->42L + f + y ()) n
[<Benchmark>]member_.MapSingletonSome()=
Inline.map (fun x -> x +43L + y ()) s
[<Benchmark>]member_.MapSingletonNone()=
Inline.map (fun x -> x +44L + y ()) n
[<Benchmark>]member_.MapSome()=
Inline.map (fun x -> x + f + y ()) s
[<Benchmark>]member_.MapNone()=
Inline.map (fun x -> x + f + y ()) n
[<MemoryDiagnoser>]typeInlineAndLambda()=lets= ValueSome (int64 System.DateTime.Now.Day)letn= ValueOption<int64>.None
[<NoCompilerInlining>]letf=10L
[<Benchmark>]member_.DefaultWithSingletonNone()=
InlineAndLambda.defaultWith (fun()->41L + y ()) n
[<Benchmark>]member_.DefaultWithNone()=
InlineAndLambda.defaultWith (fun()->42L + f + y ()) n
[<Benchmark>]member_.MapSingletonSome()=
InlineAndLambda.map (fun x -> x +43L + y ()) s
[<Benchmark>]member_.MapSingletonNone()=
InlineAndLambda.map (fun x -> x +44L + y ()) n
[<Benchmark>]member_.MapSome()=
InlineAndLambda.map (fun x -> x + f + y ()) s
[<Benchmark>]member_.MapNone()=
InlineAndLambda.map (fun x -> x + f + y ()) n
BenchmarkDotNet.Running.BenchmarkRunner.Run (
typeof<Current>.Assembly,
DefaultConfig.Instance.WithOption (ConfigOptions.JoinSummary,true))|> ignore

Decompiled

usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;using<StartupCode$Bench>;usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Reports;usingMicrosoft.FSharp.Core;[CompilationMapping(SourceConstructFlags.Module)]publicstaticclassProgram{publicstaticlongy(){if(1/1==1){return100L/100L;}return2L/3L;}[CompilerGenerated]internalstaticlongdefThunk@5(UnitunitVar0){return41L+((1/1!=1)?(2L/3L):(100L/100L));}[CompilerGenerated]internalstaticlongdefThunk@5-1(Program.Inline_,UnitunitVar0){return42L+_.f+((1/1!=1)?(2L/3L):(100L/100L));}[CompilerGenerated]internalstaticlongmapping@10(longx){returnx+43L+((1/1!=1)?(2L/3L):(100L/100L));}[CompilerGenerated]internalstaticlongmapping@10-1(longx){returnx+44L+((1/1!=1)?(2L/3L):(100L/100L));}[CompilerGenerated]internalstaticlongmapping@10-2(Program.Inline_,longx){returnx+_.f+((1/1!=1)?(2L/3L):(100L/100L));}[CompilerGenerated]internalstaticlongmapping@10-3(Program.Inline_,longx){returnx+_.f+((1/1!=1)?(2L/3L):(100L/100L));}[CompilationMapping(SourceConstructFlags.Value)]internalstaticSummary[]arg@1{get{return $Program.arg@1;}}[MemoryDiagnoser(true)][CompilationMapping(SourceConstructFlags.ObjectType)][Serializable]publicclassCurrent{publicCurrent():this(){this.s=FSharpValueOption<long>.NewValueSome((long)DateTime.Now.Day);this.n=FSharpValueOption<long>.ValueNone;this.f=10L;}[Benchmark(43,"C:\\code\\Test\\Bench\\Program.fs")]publiclongDefaultWithSingletonNone(){returnValueOption.DefaultWith<long>(Program.DefaultWithSingletonNone@45.@_instance,this.n);}[Benchmark(47,"C:\\code\\Test\\Bench\\Program.fs")]publiclongDefaultWithNone(){returnValueOption.DefaultWith<long>(newProgram.DefaultWithNone@49(this),this.n);}[Benchmark(51,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSingletonSome(){returnValueOption.Map<long,long>(Program.MapSingletonSome@53.@_instance,this.s);}[Benchmark(55,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSingletonNone(){returnValueOption.Map<long,long>(Program.MapSingletonNone@57.@_instance,this.n);}[Benchmark(59,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSome(){returnValueOption.Map<long,long>(newProgram.MapSome@61(this),this.s);}[Benchmark(63,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapNone(){returnValueOption.Map<long,long>(newProgram.MapNone@65(this),this.n);}internalFSharpValueOption<long>s;internalFSharpValueOption<long>n;[NoCompilerInlining]internallongf;}[Serializable]internalsealedclassDefaultWithSingletonNone@45:FSharpFunc<Unit,long>{[CompilerGenerated][DebuggerNonUserCode]internalDefaultWithSingletonNone@45(){}publicoverridelongInvoke(UnitunitVar0){return41L+((1/1!=1)?(2L/3L):(100L/100L));}// Note: this type is marked as 'beforefieldinit'.staticDefaultWithSingletonNone@45(){}internalstaticreadonlyProgram.DefaultWithSingletonNone@45@_instance=newProgram.DefaultWithSingletonNone@45();}[Serializable]internalsealedclassDefaultWithNone@49:FSharpFunc<Unit,long>{[CompilerGenerated][DebuggerNonUserCode]internalDefaultWithNone@49(Program.Current_){this._=_;}publicoverridelongInvoke(UnitunitVar0){return42L+this._.f+((1/1!=1)?(2L/3L):(100L/100L));}publicProgram.Current_;}[Serializable]internalsealedclassMapSingletonSome@53:FSharpFunc<long,long>{[CompilerGenerated][DebuggerNonUserCode]internalMapSingletonSome@53(){}publicoverridelongInvoke(longx){returnx+43L+((1/1!=1)?(2L/3L):(100L/100L));}// Note: this type is marked as 'beforefieldinit'.staticMapSingletonSome@53(){}internalstaticreadonlyProgram.MapSingletonSome@53@_instance=newProgram.MapSingletonSome@53();}[Serializable]internalsealedclassMapSingletonNone@57:FSharpFunc<long,long>{[CompilerGenerated][DebuggerNonUserCode]internalMapSingletonNone@57(){}publicoverridelongInvoke(longx){returnx+44L+((1/1!=1)?(2L/3L):(100L/100L));}// Note: this type is marked as 'beforefieldinit'.staticMapSingletonNone@57(){}internalstaticreadonlyProgram.MapSingletonNone@57@_instance=newProgram.MapSingletonNone@57();}[Serializable]internalsealedclassMapSome@61:FSharpFunc<long,long>{[CompilerGenerated][DebuggerNonUserCode]internalMapSome@61(Program.Current_){this._=_;}publicoverridelongInvoke(longx){returnx+this._.f+((1/1!=1)?(2L/3L):(100L/100L));}publicProgram.Current_;}[Serializable]internalsealedclassMapNone@65:FSharpFunc<long,long>{[CompilerGenerated][DebuggerNonUserCode]internalMapNone@65(Program.Current_){this._=_;}publicoverridelongInvoke(longx){returnx+this._.f+((1/1!=1)?(2L/3L):(100L/100L));}publicProgram.Current_;}[MemoryDiagnoser(true)][CompilationMapping(SourceConstructFlags.ObjectType)][Serializable]publicclassInline{publicInline():this(){this.s=FSharpValueOption<long>.NewValueSome((long)DateTime.Now.Day);this.n=FSharpValueOption<long>.ValueNone;this.f=10L;}[Benchmark(77,"C:\\code\\Test\\Bench\\Program.fs")]publiclongDefaultWithSingletonNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}returnProgram.defThunk@5(null);}[Benchmark(81,"C:\\code\\Test\\Bench\\Program.fs")]publiclongDefaultWithNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}returnProgram.defThunk@5-1(this,null);}[Benchmark(85,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSingletonSome(){FSharpValueOption<long>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){longitem=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(Program.mapping@10(item));}returnFSharpValueOption<long>.ValueNone;}[Benchmark(89,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSingletonNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){longitem=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(Program.mapping@10-1(item));}returnFSharpValueOption<long>.ValueNone;}[Benchmark(93,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSome(){FSharpValueOption<long>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){longitem=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(Program.mapping@10-2(this,item));}returnFSharpValueOption<long>.ValueNone;}[Benchmark(97,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){longitem=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(Program.mapping@10-3(this,item));}returnFSharpValueOption<long>.ValueNone;}internalFSharpValueOption<long>s;internalFSharpValueOption<long>n;[NoCompilerInlining]internallongf;}[MemoryDiagnoser(true)][CompilationMapping(SourceConstructFlags.ObjectType)][Serializable]publicclassInlineAndLambda{publicInlineAndLambda():this(){this.s=FSharpValueOption<long>.NewValueSome((long)DateTime.Now.Day);this.n=FSharpValueOption<long>.ValueNone;this.f=10L;}[Benchmark(111,"C:\\code\\Test\\Bench\\Program.fs")]publiclongDefaultWithSingletonNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}return41L+((1/1!=1)?(2L/3L):(100L/100L));}[Benchmark(115,"C:\\code\\Test\\Bench\\Program.fs")]publiclongDefaultWithNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){returnfsharpValueOption.Item;}return42L+this.f+((1/1!=1)?(2L/3L):(100L/100L));}[Benchmark(119,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSingletonSome(){FSharpValueOption<long>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){longx=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(x+43L+((1/1!=1)?(2L/3L):(100L/100L)));}returnFSharpValueOption<long>.ValueNone;}[Benchmark(123,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSingletonNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){longx=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(x+44L+((1/1!=1)?(2L/3L):(100L/100L)));}returnFSharpValueOption<long>.ValueNone;}[Benchmark(127,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapSome(){FSharpValueOption<long>fsharpValueOption=this.s;if(fsharpValueOption.Tag==1){longx=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(x+this.f+((1/1!=1)?(2L/3L):(100L/100L)));}returnFSharpValueOption<long>.ValueNone;}[Benchmark(131,"C:\\code\\Test\\Bench\\Program.fs")]publicFSharpValueOption<long>MapNone(){FSharpValueOption<long>fsharpValueOption=this.n;if(fsharpValueOption.Tag==1){longx=fsharpValueOption.Item;returnFSharpValueOption<long>.NewValueSome(x+this.f+((1/1!=1)?(2L/3L):(100L/100L)));}returnFSharpValueOption<long>.ValueNone;}internalFSharpValueOption<long>s;internalFSharpValueOption<long>n;[NoCompilerInlining]internallongf;}[CompilationMapping(SourceConstructFlags.Module)]publicstaticclassInlineAndLambdaModule{[CompilationArgumentCounts(newint[]{1,1})]publicstaticadefaultWith<a>([InlineIfLambda]FSharpFunc<Unit,a>defThunk,FSharpValueOption<a>option){if(option.Tag==1){returnoption.Item;}returndefThunk.Invoke(null);}[CompilationArgumentCounts(newint[]{1,1})]publicstaticFSharpValueOption<b>map<a,b>([InlineIfLambda]FSharpFunc<a,b>mapping,FSharpValueOption<a>option){if(option.Tag==1){ax=option.Item;returnFSharpValueOption<b>.NewValueSome(mapping.Invoke(x));}returnFSharpValueOption<b>.ValueNone;}}[CompilationMapping(SourceConstructFlags.Module)]publicstaticclassInlineModule{[CompilationArgumentCounts(newint[]{1,1})]publicstaticadefaultWith<a>(FSharpFunc<Unit,a>defThunk,FSharpValueOption<a>option){if(option.Tag==1){returnoption.Item;}returndefThunk.Invoke(null);}[CompilationArgumentCounts(newint[]{1,1})]publicstaticFSharpValueOption<b>map<a,b>(FSharpFunc<a,b>mapping,FSharpValueOption<a>option){if(option.Tag==1){ax=option.Item;returnFSharpValueOption<b>.NewValueSome(mapping.Invoke(x));}returnFSharpValueOption<b>.ValueNone;}}}

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1992/22H2/2022Update/SunValley2)
AMD Ryzen 9 7900, 1 CPU, 24 logical and 12 physical cores
.NET SDK=8.0.100-preview.6.23330.14
[Host] : .NET 8.0.0 (8.0.23.32907), X64 RyuJIT AVX2 DEBUG
DefaultJob : .NET 8.0.0 (8.0.23.32907), X64 RyuJIT AVX2
TypeMethodMeanErrorStdDevMedianGen0Allocated
CurrentDefaultWithSingletonNone30.7785 ns0.0699 ns0.0546 ns30.7517 ns--
InlineDefaultWithSingletonNone0.0061 ns0.0023 ns0.0020 ns0.0065 ns--
InlineAndLambdaDefaultWithSingletonNone0.0112 ns0.0068 ns0.0064 ns0.0081 ns--
CurrentDefaultWithNone32.9885 ns0.1077 ns0.0954 ns32.9771 ns0.001424 B
InlineDefaultWithNone0.0079 ns0.0017 ns0.0014 ns0.0079 ns--
InlineAndLambdaDefaultWithNone0.1987 ns0.0069 ns0.0065 ns0.1995 ns--
CurrentMapSingletonSome31.2620 ns0.0629 ns0.0491 ns31.2560 ns--
InlineMapSingletonSome0.2333 ns0.0028 ns0.0024 ns0.2324 ns--
InlineAndLambdaMapSingletonSome0.2293 ns0.0026 ns0.0025 ns0.2294 ns--
CurrentMapSingletonNone31.4863 ns0.1252 ns0.1046 ns31.4460 ns--
InlineMapSingletonNone0.0468 ns0.0018 ns0.0016 ns0.0467 ns--
InlineAndLambdaMapSingletonNone0.0318 ns0.0024 ns0.0022 ns0.0306 ns--
CurrentMapSome35.9461 ns0.0499 ns0.0417 ns35.9411 ns0.001424 B
InlineMapSome0.2216 ns0.0044 ns0.0041 ns0.2203 ns--
InlineAndLambdaMapSome0.2208 ns0.0053 ns0.0049 ns0.2192 ns--
CurrentMapNone38.2091 ns0.1792 ns0.1589 ns38.1327 ns0.001424 B
InlineMapNone0.2368 ns0.0016 ns0.0015 ns0.2365 ns--
InlineAndLambdaMapNone0.0373 ns0.0028 ns0.0026 ns0.0380 ns--

Analysis

My conclusion is identical to #14927 - do use both inline and InlineIfLambda. However, there is also one important anomaly to be aware of, as highlighted between the 2 sets of benchmarks.

ValueOption<int> functions are namely surprisingly slow, to the point that they might be slower than Option<int> despite the latter having to allocate. This is due to a deficiency in JIT, where it produces suboptimal machine code when structs are smaller than the register size. When we switch to ValueOption<int64>, ValueOption is clearly the winner compared to Option. The difference between status quo and inlined functions becomes even more stark (several orders of magnitude), because the non-inlined functions take a significant performance hit with int64.

This is something to keep in mind when benchmarking fsharp/fslang-suggestions#739.

@kerams
kerams requested a review from a team as a code ownerJuly 31, 2023 08:25
@T-Gro

Copy link
Copy Markdown
Member

For the default scenarios (DefaultWithSingletonNone, DefaultWithNone), doing InlineAndLambda is doing worse than Inline.
Wouldn't it then make sense to have the function inline, but not the argument?

@kerams

kerams commented Jul 31, 2023

Copy link
Copy Markdown
ContributorAuthor

I don't know. It's not massively slower in absolute terms, and who knows what the JIT would produce if the caller function and/or lambda bodies were larger. I'd use the attribute too, if only for consistency's sake.

@T-Gro

Copy link
Copy Markdown
Member

If the bodies were larger, it should probably also stay un-inlined.
You are right the absolute diff is small, and compared to non-inlined scenario this is a massive improvement either way.

Just the relative diff stands out in this case, whereas elsewhere the lambda inlining was either superior or at least on par.
image

@vzarytovskii

Copy link
Copy Markdown
Member

I think JIT should be fine in general. It should'be be a HUGE lambda for it to not inline. We probably can remove IILAttribute for the singleton version, with comment that it was slower, I guess?

@kerams

Copy link
Copy Markdown
ContributorAuthor

No, because we're talking about one ValueOption.defaultWith used either with a singleton closure or one that captures values.

@vzarytovskii

Copy link
Copy Markdown
Member

No, because we're talking about one ValueOption.defaultWith used either with a singleton closure or one that captures values.

Wait, that's what I meant, remove attribute for the defaultWith alltogether.

@kerams

Copy link
Copy Markdown
ContributorAuthor

Ok, I was confused by the mention of singleton.

@vzarytovskii

Copy link
Copy Markdown
Member

Ok, I was confused by the mention of singleton.

I'm a bit slow today, so it's possible that I thought of one thing, and wrote a different one.

@vzarytovskii
vzarytovskii enabled auto-merge (squash) July 31, 2023 14:29
@T-Gro

Copy link
Copy Markdown
Member

I ran the same benchmark and added also cases with a lot larger lambda body, as well as a case which maps over ValueSome (therefore never invoking the lambda/code).

The Inline version (without InlineIfLambda attr) was always slightly faster.

=> Would prefer to have the attr removed (as is now with the latest commit)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants

@kerams@T-Gro@vzarytovskii