I’m using VSCode and Benchmark.NET to do some benchmarking on some F# code.
In this specific case, I’d like to have two types of benchmark: “Full” (RunStrategy.Throughput) and “Quick” (RunStrategy.Monitoring with low iteration count).
For each of these types I’d like to have two sample sizes: “Small” (10,000 samples) and “Large” (1,000,000 samples).
In order to try and avoid repetition of code I tried to implement the benchmarks via an interface but it’s starting to look so ‘messy’ that I’m tempted to just copy and paste some much simpler ‘basic’ benchmark code and make a few changes to that instead.
I currently have the code below which is pretty awful to read (to me at least) and there’s lots of repetition.
Is there any way I can reduce the amount of code while still retaining the same amount of flexibility?
(You can’t run the code as it is because I haven’t included the ancillary code.)
Note: No-one told me to write it this way, it just ‘evolved’ into this to a point where it started working, so I’d be happy to tear it all down and start again if that would lead to an improvement.
open System
open FSharp.Collections.ParallelSeq
open BenchmarkDotNet.Running
open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Engines
type IBenchmark =
abstract member Sequential : unit -> int list
abstract member Parallel : unit -> int list
abstract member ParallelWithDepth : unit -> int list
abstract member ListSort : unit -> int list
abstract member ListParallel : unit -> int list
module Harness =
let smallSample = 10_000
let largeSample = 1_000_000
[<AbstractClass>]
type Base() =
let rand = Random(int(DateTime.Now.Ticks))
let mutable samples = List<int>.Empty
abstract member Sequential : unit -> int list
default _.Sequential() =
samples |> QuickSort.quicksortSequential
abstract member Parallel : unit -> int list
default _.Parallel() =
samples |> QuickSort.quicksortParallel
abstract member ParallelWithDepth : unit -> int list
default _.ParallelWithDepth() =
samples |> (QuickSort.quicksortParallelWithDepth QuickSort.ParallelismHelpers.MaxDepth)
abstract member ListSort : unit -> int list
default _.ListSort() =
samples |> List.sort
abstract member ListParallel : unit -> int list
default _.ListParallel() =
samples |> PSeq.sort |> PSeq.toList
member _.InitialiseSamples num = samples <- List.init num (fun _ -> rand.Next())
interface IBenchmark with
member this.Sequential() = this.Sequential()
member this.Parallel() = this.Parallel()
member this.ParallelWithDepth() = this.ParallelWithDepth()
member this.ListSort() = this.ListSort()
member this.ListParallel() = this.ListParallel()
[< MemoryDiagnoser >]
type FullSmall() as this =
inherit Base()
do
this.InitialiseSamples smallSample
[<Benchmark>]
override _.Sequential() = base.Sequential()
[<Benchmark>]
override _.Parallel() = base.Parallel()
[<Benchmark>]
override _.ParallelWithDepth() = base.ParallelWithDepth()
[<Benchmark>]
override _.ListSort() = base.ListSort()
[<Benchmark>]
override _.ListParallel() = base.ListParallel()
[< MemoryDiagnoser >]
type FullLarge() as this =
inherit Base()
do
this.InitialiseSamples largeSample
[<Benchmark>]
override _.Sequential() = base.Sequential()
[<Benchmark>]
override _.Parallel() = base.Parallel()
[<Benchmark>]
override _.ParallelWithDepth() = base.ParallelWithDepth()
[<Benchmark>]
override _.ListSort() = base.ListSort()
[<Benchmark>]
override _.ListParallel() = base.ListParallel()
[<
MemoryDiagnoser;
SimpleJob(RunStrategy.Monitoring, launchCount = 1, warmupCount = 3, iterationCount = 5)
>]
type QuickSmall() as this =
inherit Base()
do
this.InitialiseSamples smallSample
[<Benchmark>]
override _.Sequential() = base.Sequential()
[<Benchmark>]
override _.Parallel() = base.Parallel()
[<Benchmark>]
override _.ParallelWithDepth() = base.ParallelWithDepth()
[<Benchmark>]
override _.ListSort() = base.ListSort()
[<Benchmark>]
override _.ListParallel() = base.ListParallel()
[<
MemoryDiagnoser;
SimpleJob(RunStrategy.Monitoring, launchCount = 1, warmupCount = 3, iterationCount = 5)
>]
type QuickLarge() as this =
inherit Base()
do
this.InitialiseSamples largeSample
[<Benchmark>]
override _.Sequential() = base.Sequential()
[<Benchmark>]
override _.Parallel() = base.Parallel()
[<Benchmark>]
override _.ParallelWithDepth() = base.ParallelWithDepth()
[<Benchmark>]
override _.ListSort() = base.ListSort()
[<Benchmark>]
override _.ListParallel() = base.ListParallel()
[<EntryPoint>]
let main args =
let lowerArgs = args |> Array.map (fun s -> s.ToLower())
let runType = lowerArgs[0]
let sampleLength = lowerArgs[1]
let summary =
match runType, sampleLength with
| "full" , "small" -> BenchmarkRunner.Run<Harness.FullSmall>()
| "full" , "large" -> BenchmarkRunner.Run<Harness.FullLarge>()
| "quick" , "small" -> BenchmarkRunner.Run<Harness.QuickSmall>()
| "quick" , "large" -> BenchmarkRunner.Run<Harness.QuickLarge>()
| _ -> failwith (sprintf "!*!* Invalid Arguments: runType = %s , sampleLength = %s" runType sampleLength)
summary |> printfn "%A"
0