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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
|
(** Multicore bench is a framework for writing multicore benchmark executables
to run on {{:https://github.com/ocurrent/current-bench}current-bench}.
To use the framework one typically opens it
{[
open Multicore_bench
]}
which brings a number of submodules into scope. *)
module Trend : sig
(** Dealing with trends. *)
type t = [ `Lower_is_better | `Higher_is_better ]
(** Whether a lower or higher value is better. *)
end
module Metric : sig
(** Dealing with benchmark metrics. *)
type t
(** Represents a metric. *)
val make :
metric:string ->
config:string ->
?units:string ->
?trend:[< Trend.t ] ->
?description:string ->
[< `Float of float ] ->
t
(** [make ~metric ~config value] constructs a metric with given
specification. *)
end
module Unit_of_rate : sig
(** Dealing with units of rate. *)
type t =
[ `_1 (** 1/s *)
| `k (** 10{^ 3}/s or k/s *)
| `M (** 10{^ 6}/s or M/s *)
| `G (** 10{^ 9}/s or G/s *) ]
(** Represents a unit of rate, i.e. how many per second. *)
val to_divisor : [< t ] -> float
(** [to_divisor t] converts the unit of rate [t] to a divisor. *)
val to_mnemonic : [< t ] -> string
(** [to_mnemonic t] returns a human readable mnemonic for the unit of rate
[t]. *)
end
module Unit_of_time : sig
(** Dealing with units of time. *)
type t =
[ `s (** seconds *)
| `ms (** milliseconds *)
| `mus (** microseconds *)
| `ns (** nanoseconds *) ]
(** Represents a unit of time. *)
val to_multiplier : [< t ] -> float
(** [to_multiplier t] converts the unit of time [t] to a multiplier. *)
val to_mnemonic : [< t ] -> string
(** [to_mnemonic t] returns a human readable mnemonic for the unit of time
[t]. *)
end
module Times : sig
(** Recording timings of benchmarks running on multiple domains in parallel
and producing metrics from the recorded timings. *)
type t
(** Represents a record of elapsed times of multiple runs of a benchmark
running on multiple domains. *)
val record :
budgetf:float ->
n_domains:int ->
?ensure_multi_domain:bool ->
?domain_local_await:[< `Busy_wait | `Neglect > `Busy_wait ] ->
?n_warmups:int ->
?n_runs_min:int ->
?n_runs_max:int ->
?before:(unit -> unit) ->
init:(int -> 's) ->
?wrap:(int -> 's -> (unit -> unit) -> unit) ->
work:(int -> 's -> unit) ->
?after:(unit -> unit) ->
unit ->
t
(** [record ~budgetf ~n_domains ~init ~work ()] essentially repeatedly runs
[let x = init i in wrap i x (fun () -> .. work i x ..)] on specified
number of domains, [i ∊ [0, n_domains-1]], and records the times that
calls of [work] take. The calls of [work] are synchronized to start as
simultaneously as possible.
Optional arguments:
- [~ensure_multi_domain]: Whether to run an extra busy untimed domain when
[n_domains] is [1]. Doing so prevents the OCaml runtime from using
specialized runtime implementations. Defaults to [true].
- [~domain_local_await]: Specifies whether and how to configure
{{:https://github.com/ocaml-multicore/domain-local-await/}domain-local-await}
or DLA. [`Neglect] does not reconfigure DLA. [`Busy_wait] configures
DLA to use a busy-wait implementation, which prevents domains from going
to sleep. Defaults to [`Busy_wait].
- [~n_warmups]: Specifies the number of warmup runs to perform before the
actual measurements. Defaults to [3].
- [~n_runs_min]: Specifies the minimum number of timed runs. The upper
bound is determined dynamically based on [budgetf]. Defaults to [7].
- [~n_runs_max]: Specifies the maximum number of timed runs. Defaults to
[1023].
- [~before]: Specifies an action to run on one domain before [init].
- [~after]: Specifies an action to run on one domain after [work]. *)
val to_thruput_metrics :
n:int ->
singular:string ->
?plural:string ->
config:string ->
?unit_of_time:Unit_of_time.t ->
?unit_of_rate:Unit_of_rate.t ->
t ->
Metric.t list
(** [to_thruput_metrics ~n ~singular ~config times] produces a pair of metrics
from the recorded [times] where one metric is for the time a single
operation takes and the other is the thruput of operations over all
domains.
Optional arguments:
- [~plural]: Plural for the operation. Defaults to [singular ^ "s"].
- [~unit_of_time]: Unit of time for the duration of a single operation.
Defaults to [`ns].
- [~unit_of_rate]: Unit of rate for the number of operations per second.
Defaults to [`M]. *)
end
module Suite : sig
(** Dealing with benchmark suites. *)
type t = budgetf:float -> Metric.t list
(** Represents a benchmark suite, i.e. a function that produces a list of
metric outputs for
{{:https://github.com/ocurrent/current-bench}current-bench}. *)
end
module Cmd : sig
(** Command line interface for a benchmark executable. *)
type output =
[ `JSON
(** [`JSON] gives the JSON output for
{{:https://github.com/ocurrent/current-bench}current-bench}. *)
| `Brief (** [`Brief] gives concise human readable output. *)
| `Diff of string
(** [`Diff "path.json"] gives concise human readable diff against results
stored in specified [path.json] file. *)
]
(** Specifies the output format. *)
val run :
benchmarks:(string * Suite.t) list ->
?budgetf:float ->
?filters:string list ->
?debug:bool ->
?output:output ->
?argv:string array ->
?flush:bool ->
?randomize:bool ->
unit ->
unit
(** [run ~benchmarks ()] interprets command line arguments and runs the
benchmarks suites based on the arguments.
Optional arguments:
- [~budgetf]: A budget (usually) in seconds passed to each benchmark
suite. This defaults to a small number so that a benchmark suite can be
used as a test.
- [~filters]: A list of regular expressions to match names of benchmark
suites. If any regular expression matches the name of benchmark, then
that benchmark will be run. Defaults to [[]].
- [~debug]: Print progress information to help debugging. Defaults to
[false].
- [~output]: Output mode. Defaults to [`JSON].
- [~argv]: Array of command line arguments. Defaults to [Sys.argv].
- [~flush]: Whether to flush the standard output after writing it.
Defaults to [true].
- [~randomize]: Whether to randomize the order of suites or not. Defaults
to [true].
Command line arguments take precedence over the optional arguments. In
other words, you can specify the optional arguments to give defaults for
the benchmark executable. *)
end
module Countdown : sig
(** Scalable low-level countdown. *)
type t
(** Represents a countdown counter. *)
val create : n_domains:int -> unit -> t
(** [create ~n_domains ()] returns a new countdown counter with initial value
of [0]. *)
val non_atomic_set : t -> int -> unit
(** [non_atomic_set countdown count] sets the [count] of the [countdown].
⚠️ This operation is not atomic. However, it is safe to call
[non_atomic_set] with the same [countdown] and [count] in parallel,
because the [countdown] will be initialized deterministically. *)
val get : t -> int
(** [get countdown] returns the count of the [countdown]. *)
val alloc : t -> domain_index:int -> batch:int -> int
(** [alloc countdown ~domain_index ~batch] tries to reduce the count of the
[countdown] by at most [batch] (which must be positive) and returns the
number by which the count was reduced or [0] in case the count was already
[0]. *)
end
module Util : sig
(** Utilities for creating benchmarks.
⚠️ In the future we expect to regroup these utilities under different
modules and deprecate them in this module. *)
val iter_factor : int
(** A multiplier depending various factors such as whether we are running on a
32- or 64-bit machine (1x/10x), bytecode or native (1x/10x), and whether
we are running on single-core or multicore OCaml (1x/10x). *)
val alloc : ?batch:int -> int Atomic.t -> int
(** [alloc ~batch n] tries to decrement the specified atomic variable [n] by
at most the optional amount [~batch] and not beyond [n] having value [0].
Returns the amount by which [n] was decremented, which is [0] only in case
[n] is [0]. *)
val cross : 'a list -> 'b list -> ('a * 'b) list
(** [cross xs ys] returns a list formed by pairing each element of [xs] with
each element of [ys].
For example:
{[
# Util.cross [1; 2; 3] ["a"; "b"]
- : (int * string) list =
[(1, "a"); (1, "b"); (2, "a"); (2, "b"); (3, "a"); (3, "b")]
]} *)
module Bits : sig
(** A minimalistic bitset data structure. *)
type t
(** Represents a bitset. *)
val create : unit -> t
(** [create ()] returns a new zero length bitset. *)
val push : t -> bool -> unit
(** [push bs b] adds the bit [b] to the end of the bitset [bs]. *)
val iter : (bool -> unit) -> t -> unit
(** [iter action bs] calls the [action] for each bit in the bitset [bs]. *)
end
val generate_push_and_pop_sequence : ?state:Random.State.t -> int -> Bits.t
(** [generate_push_and_pop_sequence n] generates a bitset where each [true]
bit represents a "push" operation and each [false] bit represents a
"try_pop" operation. Performing the operations on an initially empty
dispenser leaves the dispenser empty. The sequence may include "try_pop"
operations at points where the dispenser will be empty. *)
end
|