-
Notifications
You must be signed in to change notification settings - Fork 786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Inline results module #16106
Inline results module #16106
Conversation
@dotnet-policy-service agree |
@bradcypert I'm not fully aware of the background, but it looks like the RFC is about functions that take a lambda and call it at most once. There are other functions that seem to be adjusted to inline in your current PR ( Hopefully someone else will confirm, the conservative approach is to stick to only those where you applied Thanks for joining the contributors to this repository! |
I based my changes off the changes to the option type in a previous PR. It also involved making those other functions inline as well, but I'm happy to revert my changes for the functions such as ToArray, etc. |
Would also like to see some beanchmark results, like it was done for Option change. |
/azp run |
Azure Pipelines successfully started running 2 pipeline(s). |
I'd be happy to add some benchmarks, but I'll need to learn more about that process to successfully be able to do that. Are benchmarks ran on a dedicated machine? Sorry, I haven't ran DotNet benchmarks before so this is all a little new to me :) |
I think @kerams was testing changes for Option.
Alternatively, to have more precise results and run in "one go", the following can be done (more sophisticated):
|
That would be the most straightforward way, I'd say. Have two copies - with and without changes. |
I included the entire benchmarking code in my PRs. |
Benchmarking Codeopen BenchmarkDotNet.Attributes
open BenchmarkDotNet.Configs
module Inline =
let inline map mapping result =
match result with
| Error e -> Error e
| Ok x -> Ok(mapping x)
let inline mapError mapping result =
match result with
| Error e -> Error(mapping e)
| Ok x -> Ok x
let inline bind binder result =
match result with
| Error e -> Error e
| Ok x -> binder x
let inline defaultValue value result =
match result with
| Error _ -> value
| Ok v -> v
let inline defaultWith defThunk result =
match result with
| Error error -> defThunk error
| Ok v -> v
let inline count result =
match result with
| Error _ -> 0
| Ok _ -> 1
let inline fold<'T, 'Error, 'State> folder (state: 'State) (result: Result<'T, 'Error>) =
match result with
| Error _ -> state
| Ok x -> folder state x
let inline foldBack<'T, 'Error, 'State> folder (result: Result<'T, 'Error>) (state: 'State) =
match result with
| Error _ -> state
| Ok x -> folder x state
let inline exists predicate result =
match result with
| Error _ -> false
| Ok x -> predicate x
let inline forall predicate result =
match result with
| Error _ -> true
| Ok x -> predicate x
let inline iter action result =
match result with
| Error _ -> ()
| Ok x -> action x
let inline toArray result =
match result with
| Error _ -> [||]
| Ok x -> [| x |]
let inline toList result =
match result with
| Error _ -> []
| Ok x -> [ x ]
let inline toOption result =
match result with
| Error _ -> None
| Ok x -> Some x
let inline toValueOption result =
match result with
| Error _ -> ValueNone
| Ok x -> ValueSome x
module InlineAndLambda =
let inline map ([<InlineIfLambda>] mapping) result =
match result with
| Error e -> Error e
| Ok x -> Ok(mapping x)
let inline mapError ([<InlineIfLambda>] mapping) result =
match result with
| Error e -> Error(mapping e)
| Ok x -> Ok x
let inline bind ([<InlineIfLambda>] binder) result =
match result with
| Error e -> Error e
| Ok x -> binder x
let inline defaultWith ([<InlineIfLambda>] defThunk) result =
match result with
| Error error -> defThunk error
| Ok v -> v
let inline fold<'T, 'Error, 'State> ([<InlineIfLambda>] folder) (state: 'State) (result: Result<'T, 'Error>) =
match result with
| Error _ -> state
| Ok x -> folder state x
let inline foldBack<'T, 'Error, 'State> ([<InlineIfLambda>] folder) (result: Result<'T, 'Error>) (state: 'State) =
match result with
| Error _ -> state
| Ok x -> folder x state
let inline exists ([<InlineIfLambda>] predicate) result =
match result with
| Error _ -> false
| Ok x -> predicate x
let inline forall ([<InlineIfLambda>] predicate) result =
match result with
| Error _ -> true
| Ok x -> predicate x
let inline iter ([<InlineIfLambda>] action) result =
match result with
| Error _ -> ()
| Ok x -> action x
// Some function with a bunch of instructions that isn't going to cause lambda inlining without InlineIfLambda
let inline y () =
if 1 / 1 = 1 then
100 / 100
else
2 / 3
[<MemoryDiagnoser>]
type Current () =
let r = Ok (System.DateTime.Now.Day)
let e = Error ()
[<NoCompilerInlining>]
let f = 10
[<Benchmark>]
member _.DefaultWithSingletonError() =
Result.defaultWith (fun () -> 41 + y ()) e
[<Benchmark>]
member _.DefaultWithError() =
Result.defaultWith (fun () -> 42 + f + y ()) e
[<Benchmark>]
member _.MapSingletonOk() =
Result.map (fun x -> x + 43 + y ()) r
[<Benchmark>]
member _.MapSingletonError() =
Result.map (fun x -> x + 44 + y ()) e
// [<Benchmark>]
// member _.BindWithError() =
// Result.bind (fun x -> Ok x) e
// [<Benchmark>]
// member _.BindWithOk() =
// Result.bind (fun x -> Ok x) r
[<Benchmark>]
member _.CountError() =
Result.count e
[<Benchmark>]
member _.CountOk() =
Result.count r
[<Benchmark>]
member _.MapOk() =
Result.map (fun x -> x + f + y ()) r
[<Benchmark>]
member _.MapError() =
Result.map (fun x -> x + f + y ()) e
// [<Benchmark>]
// member _.ToArrayError() =
// Result.toArray e
// [<Benchmark>]
// member _.ToArrayOk() =
// Result.toArray r
// [<Benchmark>]
// member _.ToListError() =
// Result.toList e
// [<Benchmark>]
// member _.ToListOk() =
// Result.toList r
// [<Benchmark>]
// member _.ToOptionError() =
// Result.toOption e
// [<Benchmark>]
// member _.ToOptionOk() =
// Result.toOption r
// [<Benchmark>]
// member _.ToValueOptionError() =
// Result.toValueOption e
// [<Benchmark>]
// member _.ToValueOptionOk() =
// Result.toValueOption r
[<MemoryDiagnoser>]
type Inline () =
let r = Ok (System.DateTime.Now.Day)
let e = Error ()
[<NoCompilerInlining>]
let f = 10
[<Benchmark>]
member _.DefaultWithSingletonError() =
Inline.defaultWith (fun () -> 41 + y ()) e
[<Benchmark>]
member _.DefaultWithError() =
Inline.defaultWith (fun () -> 42 + f + y ()) e
[<Benchmark>]
member _.MapSingletonOk() =
Inline.map (fun x -> x + 43 + y ()) r
[<Benchmark>]
member _.MapSingletonError() =
Inline.map (fun x -> x + 44 + y ()) e
// [<Benchmark>]
// member _.BindWithError() =
// Inline.bind (fun x -> Ok x) e
// [<Benchmark>]
// member _.BindWithOk() =
// Inline.bind (fun x -> Ok x) r
[<Benchmark>]
member _.CountError() =
Inline.count e
[<Benchmark>]
member _.CountOk() =
Inline.count r
[<Benchmark>]
member _.MapOk() =
Inline.map (fun x -> x + f + y ()) r
[<Benchmark>]
member _.MapError() =
Inline.map (fun x -> x + f + y ()) e
// [<Benchmark>]
// member _.ToArrayError() =
// Inline.toArray e
// [<Benchmark>]
// member _.ToArrayOk() =
// Inline.toArray r
// [<Benchmark>]
// member _.ToListError() =
// Inline.toList e
// [<Benchmark>]
// member _.ToListOk() =
// Inline.toList r
// [<Benchmark>]
// member _.ToOptionError() =
// Inline.toOption e
// [<Benchmark>]
// member _.ToOptionOk() =
// Inline.toOption r
// [<Benchmark>]
// member _.ToValueOptionError() =
// Inline.toValueOption e
// [<Benchmark>]
// member _.ToValueOptionOk() =
// Inline.toValueOption r
[<MemoryDiagnoser>]
type InlineAndLambda () =
let r = Ok (System.DateTime.Now.Day)
let e = Error ()
[<NoCompilerInlining>]
let f = 10
[<Benchmark>]
member _.DefaultWithSingletonError() =
InlineAndLambda.defaultWith (fun () -> 41 + y ()) e
[<Benchmark>]
member _.DefaultWithError() =
InlineAndLambda.defaultWith (fun () -> 42 + f + y ()) e
[<Benchmark>]
member _.MapSingletonOk() =
InlineAndLambda.map (fun x -> x + 43 + y ()) r
[<Benchmark>]
member _.MapSingletonError() =
InlineAndLambda.map (fun x -> x + 44 + y ()) e
// [<Benchmark>]
// member _.BindWithError() =
// InlineAndLambda.bind (fun x -> Ok x) e
[<Benchmark>]
member _.BindWithOk() =
InlineAndLambda.bind (fun x -> Ok x) r
[<Benchmark>]
member _.MapOk() =
InlineAndLambda.map (fun x -> x + f + y ()) r
[<Benchmark>]
member _.MapError() =
InlineAndLambda.map (fun x -> x + f + y ()) e
BenchmarkDotNet.Running.BenchmarkRunner.Run (
typeof<Current>.Assembly,
DefaultConfig.Instance.WithOption (ConfigOptions.JoinSummary, true))
|> ignore I commented out a few of the benchmarks that I tried to run, but was receiving an error about benchmarking generics. Output
// * Warnings * // * Hints * // * Legends * // * Diagnostic Output - MemoryDiagnoser * // ***** BenchmarkRunner: End ***** I know I am missing a few different benchmarks but am currently blocked until I figure out the benchmarking with generics:
and I also wanted to get feedback to make sure the benchmarking is headed in the right direction. I will keep digging into the generics error message this weekend. |
Instead of Very diligent of you to benchmark every single method:). Please rerun it all with .NET 8 rc 2 though. |
@kerams Thank you for your guidance here! I was able to get that issue resolved and have updated the benchmark code and results and am sharing those below :) Benchmark codeopen BenchmarkDotNet.Attributes
open BenchmarkDotNet.Configs
module Inline =
let inline map mapping result =
match result with
| Error e -> Error e
| Ok x -> Ok(mapping x)
let inline mapError mapping result =
match result with
| Error e -> Error(mapping e)
| Ok x -> Ok x
let inline bind binder result =
match result with
| Error e -> Error e
| Ok x -> binder x
let inline defaultValue value result =
match result with
| Error _ -> value
| Ok v -> v
let inline defaultWith defThunk result =
match result with
| Error error -> defThunk error
| Ok v -> v
let inline count result =
match result with
| Error _ -> 0
| Ok _ -> 1
let inline fold<'T, 'Error, 'State> folder (state: 'State) (result: Result<'T, 'Error>) =
match result with
| Error _ -> state
| Ok x -> folder state x
let inline foldBack<'T, 'Error, 'State> folder (result: Result<'T, 'Error>) (state: 'State) =
match result with
| Error _ -> state
| Ok x -> folder x state
let inline exists predicate result =
match result with
| Error _ -> false
| Ok x -> predicate x
let inline forall predicate result =
match result with
| Error _ -> true
| Ok x -> predicate x
let inline iter action result =
match result with
| Error _ -> ()
| Ok x -> action x
let inline toArray result =
match result with
| Error _ -> [||]
| Ok x -> [| x |]
let inline toList result =
match result with
| Error _ -> []
| Ok x -> [ x ]
let inline toOption result =
match result with
| Error _ -> None
| Ok x -> Some x
let inline toValueOption result =
match result with
| Error _ -> ValueNone
| Ok x -> ValueSome x
module InlineAndLambda =
let inline map ([<InlineIfLambda>] mapping) result =
match result with
| Error e -> Error e
| Ok x -> Ok(mapping x)
let inline mapError ([<InlineIfLambda>] mapping) result =
match result with
| Error e -> Error(mapping e)
| Ok x -> Ok x
let inline bind ([<InlineIfLambda>] binder) result =
match result with
| Error e -> Error e
| Ok x -> binder x
let inline defaultWith ([<InlineIfLambda>] defThunk) result =
match result with
| Error error -> defThunk error
| Ok v -> v
let inline fold<'T, 'Error, 'State> ([<InlineIfLambda>] folder) (state: 'State) (result: Result<'T, 'Error>) =
match result with
| Error _ -> state
| Ok x -> folder state x
let inline foldBack<'T, 'Error, 'State> ([<InlineIfLambda>] folder) (result: Result<'T, 'Error>) (state: 'State) =
match result with
| Error _ -> state
| Ok x -> folder x state
let inline exists ([<InlineIfLambda>] predicate) result =
match result with
| Error _ -> false
| Ok x -> predicate x
let inline forall ([<InlineIfLambda>] predicate) result =
match result with
| Error _ -> true
| Ok x -> predicate x
let inline iter ([<InlineIfLambda>] action) result =
match result with
| Error _ -> ()
| Ok x -> action x
// Some function with a bunch of instructions that isn't going to cause lambda inlining without InlineIfLambda
let inline y () =
if 1 / 1 = 1 then
100 / 100
else
2 / 3
[<MemoryDiagnoser>]
type Current () =
let r = Result<int, unit>.Ok (System.DateTime.Now.Day)
let e = Result<int, unit>.Error ()
[<NoCompilerInlining>]
let f = 10
[<Benchmark>]
member _.DefaultWithSingletonError() =
Result.defaultWith (fun () -> 41 + y ()) e
[<Benchmark>]
member _.DefaultWithError() =
Result.defaultWith (fun () -> 42 + f + y ()) e
[<Benchmark>]
member _.MapSingletonOk() =
Result.map (fun x -> x + 43 + y ()) r
[<Benchmark>]
member _.MapSingletonError() =
Result.map (fun x -> x + 44 + y ()) e
[<Benchmark>]
member _.BindWithError() =
Result.bind (fun x -> Ok x) e
[<Benchmark>]
member _.BindWithOk() =
Result.bind (fun x -> Ok x) r
[<Benchmark>]
member _.CountError() =
Result.count e
[<Benchmark>]
member _.CountOk() =
Result.count r
[<Benchmark>]
member _.MapOk() =
Result.map (fun x -> x + f + y ()) r
[<Benchmark>]
member _.MapError() =
Result.map (fun x -> x + f + y ()) e
[<Benchmark>]
member _.FoldOk() =
Result.fold (fun acc x -> acc + x + f + y ()) 0 r
[<Benchmark>]
member _.FoldError() =
Result.fold (fun acc x -> acc + x + f + y ()) 0 e
[<Benchmark>]
member _.FoldBackOk() =
Result.foldBack (fun x acc -> acc + x + f + y ()) r 0
[<Benchmark>]
member _.FoldBackError() =
Result.foldBack (fun x acc -> acc + x + f + y ()) e 0
[<Benchmark>]
member _.ExistsOk() =
Result.exists (fun x -> x + f + y () > 5) r
[<Benchmark>]
member _.ExistsError() =
Result.exists (fun x -> x + f + y () > 5) e
[<Benchmark>]
member _.ForAllOk() =
Result.forall (fun x -> x + f + y () > 5) r
[<Benchmark>]
member _.ForAllError() =
Result.forall (fun x -> x + f + y () > 5) e
[<Benchmark>]
member _.ToIterError() =
Result.iter (fun x -> x + f + y () > 5 |> ignore) e
[<Benchmark>]
member _.ToIterOk() =
Result.iter (fun x -> x + f + y () > 5 |> ignore) r
[<Benchmark>]
member _.ToArrayError() =
Result.toArray e
[<Benchmark>]
member _.ToArrayOk() =
Result.toArray r
[<Benchmark>]
member _.ToListError() =
Result.toList e
[<Benchmark>]
member _.ToListOk() =
Result.toList r
[<Benchmark>]
member _.ToOptionError() =
Result.toOption e
[<Benchmark>]
member _.ToOptionOk() =
Result.toOption r
[<Benchmark>]
member _.ToValueOptionError() =
Result.toValueOption e
[<Benchmark>]
member _.ToValueOptionOk() =
Result.toValueOption r
[<MemoryDiagnoser>]
type Inline () =
let r = Result<int, unit>.Ok (System.DateTime.Now.Day)
let e = Result<int, unit>.Error ()
[<NoCompilerInlining>]
let f = 10
[<Benchmark>]
member _.DefaultWithSingletonError() =
Inline.defaultWith (fun () -> 41 + y ()) e
[<Benchmark>]
member _.DefaultWithError() =
Inline.defaultWith (fun () -> 42 + f + y ()) e
[<Benchmark>]
member _.MapSingletonOk() =
Inline.map (fun x -> x + 43 + y ()) r
[<Benchmark>]
member _.MapSingletonError() =
Inline.map (fun x -> x + 44 + y ()) e
[<Benchmark>]
member _.BindWithError() =
Inline.bind (fun x -> Ok x) e
[<Benchmark>]
member _.BindWithOk() =
Inline.bind (fun x -> Ok x) r
[<Benchmark>]
member _.CountError() =
Inline.count e
[<Benchmark>]
member _.CountOk() =
Inline.count r
[<Benchmark>]
member _.MapOk() =
Inline.map (fun x -> x + f + y ()) r
[<Benchmark>]
member _.MapError() =
Inline.map (fun x -> x + f + y ()) e
[<Benchmark>]
member _.FoldOk() =
Inline.fold (fun acc x -> acc + x + f + y ()) 0 r
[<Benchmark>]
member _.FoldError() =
Inline.fold (fun acc x -> acc + x + f + y ()) 0 e
[<Benchmark>]
member _.FoldBackOk() =
Inline.foldBack (fun x acc -> acc + x + f + y ()) r 0
[<Benchmark>]
member _.FoldBackError() =
Inline.foldBack (fun x acc -> acc + x + f + y ()) e 0
[<Benchmark>]
member _.ExistsOk() =
Inline.exists (fun x -> x + f + y () > 5) r
[<Benchmark>]
member _.ExistsError() =
Inline.exists (fun x -> x + f + y () > 5) e
[<Benchmark>]
member _.ForAllOk() =
Inline.forall (fun x -> x + f + y () > 5) r
[<Benchmark>]
member _.ForAllError() =
Inline.forall (fun x -> x + f + y () > 5) e
[<Benchmark>]
member _.ToIterError() =
Inline.iter (fun x -> x + f + y () > 5 |> ignore) e
[<Benchmark>]
member _.ToIterOk() =
Inline.iter (fun x -> x + f + y () > 5 |> ignore) r
[<Benchmark>]
member _.ToArrayError() =
Inline.toArray e
[<Benchmark>]
member _.ToArrayOk() =
Inline.toArray r
[<Benchmark>]
member _.ToListError() =
Inline.toList e
[<Benchmark>]
member _.ToListOk() =
Inline.toList r
[<Benchmark>]
member _.ToOptionError() =
Inline.toOption e
[<Benchmark>]
member _.ToOptionOk() =
Inline.toOption r
[<Benchmark>]
member _.ToValueOptionError() =
Inline.toValueOption e
[<Benchmark>]
member _.ToValueOptionOk() =
Inline.toValueOption r
[<MemoryDiagnoser>]
type InlineAndLambda () =
let r = Result<int, unit>.Ok (System.DateTime.Now.Day)
let e = Result<int, unit>.Error ()
[<NoCompilerInlining>]
let f = 10
[<Benchmark>]
member _.DefaultWithSingletonError() =
InlineAndLambda.defaultWith (fun () -> 41 + y ()) e
[<Benchmark>]
member _.DefaultWithError() =
InlineAndLambda.defaultWith (fun () -> 42 + f + y ()) e
[<Benchmark>]
member _.MapSingletonOk() =
InlineAndLambda.map (fun x -> x + 43 + y ()) r
[<Benchmark>]
member _.MapSingletonError() =
InlineAndLambda.map (fun x -> x + 44 + y ()) e
[<Benchmark>]
member _.BindWithError() =
InlineAndLambda.bind (fun x -> Ok x) e
[<Benchmark>]
member _.BindWithOk() =
InlineAndLambda.bind (fun x -> Ok x) r
[<Benchmark>]
member _.MapOk() =
InlineAndLambda.map (fun x -> x + f + y ()) r
[<Benchmark>]
member _.MapError() =
InlineAndLambda.map (fun x -> x + f + y ()) e
[<Benchmark>]
member _.FoldOk() =
InlineAndLambda.fold (fun acc x -> acc + x + f + y ()) 0 r
[<Benchmark>]
member _.FoldError() =
InlineAndLambda.fold (fun acc x -> acc + x + f + y ()) 0 e
[<Benchmark>]
member _.FoldBackOk() =
InlineAndLambda.foldBack (fun x acc -> acc + x + f + y ()) r 0
[<Benchmark>]
member _.FoldBackError() =
InlineAndLambda.foldBack (fun x acc -> acc + x + f + y ()) e 0
[<Benchmark>]
member _.ExistsOk() =
InlineAndLambda.exists (fun x -> x + f + y () > 5) r
[<Benchmark>]
member _.ExistsError() =
InlineAndLambda.exists (fun x -> x + f + y () > 5) e
[<Benchmark>]
member _.ForAllOk() =
InlineAndLambda.forall (fun x -> x + f + y () > 5) r
[<Benchmark>]
member _.ForAllError() =
InlineAndLambda.forall (fun x -> x + f + y () > 5) e
[<Benchmark>]
member _.ToIterError() =
InlineAndLambda.iter (fun x -> x + f + y () > 5 |> ignore) e
[<Benchmark>]
member _.ToIterOk() =
InlineAndLambda.iter (fun x -> x + f + y () > 5 |> ignore) r
BenchmarkDotNet.Running.BenchmarkRunner.Run (
typeof<Current>.Assembly,
DefaultConfig.Instance.WithOption (ConfigOptions.JoinSummary, true))
|> ignore Benchmark Results
// * Warnings * // * Hints * // * Legends * // * Diagnostic Output - MemoryDiagnoser * // ***** BenchmarkRunner: End ***** |
I am curious about the warnings and errors from my benchmarking. I will run them again but I noticed several issues that look like they resulted in 0ms benchmarks. I'll also do some digging into that error message and BenchmarkDotNet |
I could be wrong, but it looks like those "errors" are from a benchmark that spends less an a CPU cycle on the operation. dotnet/BenchmarkDotNet#1167 |
* Inline results module * adjust fsi file too --------- Co-authored-by: Vlad Zarytovskii <vzaritovsky@hotmail.com> Co-authored-by: Tomas Grosup <tomasgrosup@microsoft.com>
Inline the results module functions similar to the issues: #14927 and #15709.
New to contributing to F# so please let me know if I need to accept a CLA, need to run any specific tooling, or have just generally missed the mark with this one :)
Thanks and look forward to contributing to such an awesome language!