Author Cburnett at English Wikipedia (GNU Free Documentation License, Version 1.2)

Code Snippets

default.nix

# Version by Robert (rycee) to point to a specific LTS version
/* Version from Robert (rycee): */
{ pkgs ? import <nixpkgs> {
  overlays = [(
    self: super: {
      dotnet-sdk-lts = super.dotnet-sdk.overrideAttrs (
        old: rec {
          version = "3.1.201";
          src = super.fetchurl {
            url = "https://dotnetcli.azureedge.net/dotnet/Sdk/${version}/dotnet-sdk-${version}-linux-x64.tar.gz";
            sha256 = "222f5363d2ab9f2aa852846bc0745c449677d1cccf8c8407cd0a44d3299cc7be";
          };
        }
      );
    }
  )];
}}:

with pkgs;

mkShell {
  buildInputs = [
    ncurses # for `clear`
    emacs
    dotnet-sdk-lts
  ];
}

# References:
#
# Download .NET Core > Download .NET Core 3.1 (LTS):
#
# - https://dotnet.microsoft.com/download/dotnet-core
#
# - https://dotnet.microsoft.com/download/dotnet-core/3.1
[…@nixosT480:~/code/dotnet/dotnet/performance]$ nix-shell --pure
[nix-shell:~/code/dotnet/dotnet/performance]$

threadpool.fsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env -S dotnet fsi --mlcompatibility --optimize --warnaserror+:25,26

(* This construct is for ML compatibility. The syntax '(typ,...,typ) ident'
   is not used in F# code. Consider using 'ident<typ,...,typ>' instead. *)
#nowarn "62"

[<RequireQualifiedAccess>]
module GarbageCollection =
  
  (* Swapping type-safety for high performance using compiler directives:
     - https://fsharpforfunandprofit.com/posts
              /typesafe-performance-with-compiler-directives
              /#timing-the-two-implementations
  *)
  
  open System
  
  let inline reset n =
    match n with
      | None   -> GC.Collect ( )
      | Some v -> GC.Collect  v
    GC.WaitForPendingFinalizers ( )
  
  let inline gen00 () = GC.CollectionCount 0
  let inline gen01 () = GC.CollectionCount 1
  let inline gen02 () = GC.CollectionCount 2
  let inline mem   () = GC.GetTotalMemory  false
  
  let inline print () =
    sprintf "GC gen0: %i, gen1: %i, gen2: %i, mem: %i bytes."
      (gen00 ()) (gen01 ()) (gen02 ()) (mem ())
  
[<RequireQualifiedAccess>]
module Performance =
  
  (* C#: Is this benchmarking class accurate?
     - https://stackoverflow.com/a/1508597
  *)
  
  open System.Diagnostics
  
  let inline time f =
    let              h = Stopwatch.IsHighResolution
    let              w = Stopwatch ()
    let inline start _ = w.Restart ()
    let inline stop  _ = w.Stop    (); w.Elapsed.TotalSeconds
    
    GarbageCollection.reset None
    let struct (bg0, bg1, bg2, bm) =
      struct
        ( GarbageCollection.gen00 ()
        , GarbageCollection.gen01 ()
        , GarbageCollection.gen02 ()
        , GarbageCollection.mem   ()
        )
    let _ = start ()
    let _ = f     ()
    let s = stop  ()
    let struct (ag0, ag1, ag2, am) =
      struct
        ( GarbageCollection.gen00 ()
        , GarbageCollection.gen01 ()
        , GarbageCollection.gen02 ()
        , GarbageCollection.mem   ()
        )
    GarbageCollection.reset None
    sprintf
      "hf: %b, time: %9f, GC gen0: %i, gen1: %i, gen2: %i, mem: %i."
        h s (ag0-bg0) (ag1-bg1)  (ag2-bg2) (am-bm) 

[<RequireQualifiedAccess>]
module Thread =
  
  open System.Threading
  
  type [<Struct>] 'a t =
    { state  : 'a state ref
    ; thread : thread
    }
  and  [<Struct>] 'a state  = private S of 'a option
  and  [<Struct>]    thread = private T of    Thread
  
  let inline fork f =
    let aux (g : unit -> unit) =
      let t = Thread g
      t.IsBackground <- true
      t.Start ()
      t
    let        s    = ref (S  None         )
    let inline h () = s:= (S (Some (f () )))
    { state  = s
    ; thread = T (aux h)
    }
  
  let inline join { state = sr; thread = (T t) } =
    t.Join()
    let (S so) = !sr
    Option.get so
  
  let inline mtid { thread = (T t) } =
    t.ManagedThreadId
  
  [<RequireQualifiedAccess>]
  module Util =
    
    let inline cores _ =
      System.Environment.ProcessorCount
    
    let inline sleep n =
      Thread.Sleep (millisecondsTimeout = n)
  
  [<RequireQualifiedAccess>]
  module Pool =
    
    [<RequireQualifiedAccess>]
    module Atomic =
    
      let inline inc (i:int ref) = Interlocked.Increment i
    
    let inline map ts f a =
      (* Inspired by "F# for Scientists" by Jon Harrop (978-0470242117)
         but using an atomic increment (interlocked) for the index counter
      *)
      let n = Array.length a
      let b = Array.create n None
      let i = ref -1
      let rec app j = if j < n then b.[j] <- Some (f a.[j]); aux (            )
      and     aux _ =                                        app (Atomic.inc i)
      Array.init ts (fun _ -> fork aux)
      |> Array.iter           join
      Array.choose id b

let _ =
  let inline f i = Thread.Util.sleep 1; (+) 1
  printfn "# Sequential"
  printfn "## f i = Thread.Util.sleep 1; (+) 1"
  printfn "## Array.create 10_000 0 |> Array.map f"
  fun ___ ->  Array.create 10_000 0 |> Array.map f
  |> Performance.time
  |> printfn "%s"

let _ =
  let        c   = Thread.Util.cores ()
  let inline f i = Thread.Util.sleep 1; (+) 1
  printfn "# Concurrent"
  printfn "## c   = Thread.Util.cores ()"
  printfn "%i" c
  printfn "## f i = Thread.Util.sleep 1; (+) 1"
  printfn "## Array.create 10_000 0 |> Thread.Pool.map c f"
  fun ___ ->  Array.create 10_000 0 |> Thread.Pool.map c f
  |> Performance.time
  |> printfn "%s"

Code Output:

[nix-shell:~/code/dotnet/dotnet/performance]$ ./threadpool.fsx 
# Sequential
## f i = Thread.Util.sleep 1; (+) 1
## Array.create 10_000 0 |> Array.map f
hf: true, time: 10.849655, GC gen0: 0, gen1: 0, gen2: 0, mem: 374016.
# Concurrent
## c   = Thread.Util.cores ()
8
## f i = Thread.Util.sleep 1; (+) 1
## Array.create 10_000 0 |> Thread.Pool.map c f
hf: true, time:  1.357901, GC gen0: 0, gen1: 0, gen2: 0, mem: 815200.

References: