Last active
August 29, 2015 14:25
-
-
Save blacktaxi/47aaec55b00fcff91827 to your computer and use it in GitHub Desktop.
F#: DU vs Struct performance
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
> System.GC.Collect();; | |
Real: 00:00:00.121, CPU: 00:00:00.109, GC gen0: 1, gen1: 1, gen2: 1 | |
val it : unit = () | |
> createTestNoArray 100000000 StructDU.gen StructDU.fold () ();; | |
Real: 00:00:04.036, CPU: 00:00:04.046, GC gen0: 534, gen1: 0, gen2: 0 | |
val it : unit = () | |
> System.GC.Collect();; | |
Real: 00:00:00.035, CPU: 00:00:00.031, GC gen0: 1, gen1: 1, gen2: 1 | |
val it : unit = () | |
> createTestNoArray 100000000 RegularDU.gen RegularDU.fold () ();; | |
Real: 00:00:04.208, CPU: 00:00:04.218, GC gen0: 534, gen1: 1, gen2: 0 | |
val it : unit = () | |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Testing regular DU... timing overhead... timing benchmark... done! | |
-> 0.0005123956951 / 1951.616709 | |
Testing struct 'DU'... timing overhead... timing benchmark... done! | |
-> 0.0008053143967 / 1241.751053 | |
Testing regular DU, no array... timing overhead... timing benchmark... done! | |
-> 0.0008429675596 / 1186.285271 | |
Testing struct 'DU', no array... timing overhead... timing benchmark... done! | |
-> 0.0008053096749 / 1241.758334 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Test = | |
open System | |
open System.Diagnostics | |
let bench (actionFactory : unit -> (unit -> unit), | |
minIterations : int option) = | |
let minIterations = defaultArg minIterations 1 | |
let properTime (action : unit -> unit) | |
(minTime : TimeSpan option) | |
(minIterations : int) = | |
let time f = | |
let sw = new System.Diagnostics.Stopwatch() | |
sw.Start() | |
f () | |
sw.Stop() | |
sw.Elapsed | |
let minTime = defaultArg minTime (TimeSpan.FromSeconds(1.0)) | |
let totalTime = ref <| TimeSpan.FromSeconds(0.0) | |
let mutable totalIterations = 0 | |
System.GC.Collect() | |
while !totalTime <= minTime do | |
totalTime := (!totalTime).Add(time <| fun () -> | |
for _ in 0 .. minIterations do | |
action ()) | |
totalIterations <- totalIterations + minIterations | |
(!totalTime, totalIterations) | |
printf "timing overhead... " | |
let (overheadT, overheadI) = properTime (fun () -> ()) None 10000000 | |
let overheadP = overheadT.TotalSeconds / (float overheadI) | |
printf "timing benchmark... " | |
let (benchT, benchI) = properTime (actionFactory ()) None minIterations | |
let benchP = benchT.TotalSeconds / (float benchI) | |
printf "done! " | |
benchP - overheadP | |
let createTest iters gen fold = | |
let af () = fun () -> | |
let d = Array.init iters gen | |
Array.fold fold 0 d |> ignore | |
af | |
let createTestNoArray iters gen fold = | |
let af () = fun () -> | |
let mutable state = 0 | |
for i = 0 to iters do | |
state <- gen i |> fold state | |
() | |
af | |
let runTests cases = | |
for (name, af) in cases do | |
printf "Testing %s... " name | |
let p = bench (af, None) | |
printfn "\n-> %A / %A" p (1.0 / p) | |
module RegularDU = | |
type Result<'a, 'e> = | |
| Ok of 'a | |
| Error of 'e | |
let gen idx = | |
if idx % 2 = 0 then Ok idx | |
else Error false | |
let fold a b = | |
match b with | |
| Ok x -> a + x | |
| Error _ -> a | |
module StructDU = | |
[<Struct>] | |
type Result<'a, 'e> = | |
val Ok : 'a | |
val Error : 'e | |
val IsOk : bool | |
new(ok, err, isOk) = { Ok = ok; Error = err; IsOk = isOk } | |
let inline Ok x = Result(x, Unchecked.defaultof<_>, true) | |
let inline Error e = Result(Unchecked.defaultof<_>, e, false) | |
let inline (|Ok|Error|) (r : Result<_, _>) = | |
if r.IsOk then Ok r.Ok | |
else Error r.Error | |
let gen idx = | |
if idx % 2 = 0 then Ok idx | |
else Error false | |
let fold a b = | |
match b with | |
| Ok x -> a + x | |
| Error _ -> a | |
let tests = [ | |
"regular DU", createTest 10000 RegularDU.gen RegularDU.fold; | |
"struct 'DU'", createTest 10000 StructDU.gen StructDU.fold; | |
"regular DU, no array", createTestNoArray 10000 RegularDU.gen RegularDU.fold; | |
"struct 'DU', no array", createTestNoArray 10000 StructDU.gen StructDU.fold; | |
] | |
runTests tests |
If I increase the number of iterations to 100000
, I get this:
Testing regular DU... timing overhead... timing benchmark... done!
-> 0.02042700855 / 48.95479422
Testing struct 'DU'... timing overhead... timing benchmark... done!
-> 0.008006895147 / 124.8923561
Testing regular DU, no array... timing overhead... timing benchmark... done!
-> 0.008723692274 / 114.6303616
Testing struct 'DU', no array... timing overhead... timing benchmark... done!
-> 0.008351817527 / 119.7344167
Testing just struct, no array... timing overhead... timing benchmark... done!
-> 0.006903679734 / 144.8502883
Testing just non-generic struct, no array... timing overhead... timing benchmark... done!
-> 0.006558729743 / 152.4685479
Strange indeed. But I'm pretty sure the benchmark is flawed.
Last two tests I've added: 'just struct' is the same as yours 'no patternmatch', and 'non-generic' is the same but the Result type is also non-generic.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Strange, I'm seeing much different results: