Micro-benchmarking in OCaml

Tuesday, Nov 10, 2020

It is instructive to understand time and space requirements of functions we write and micro-benchmarking tools of a language helps us accomplish this task. Ocaml has a library called Core_bench for this task. Let's see how it works.

In the example code below, we do three things. First, we open two modules - Core and Core_bench. Next, we create two functions that we want to benchmark - sum of n natural numbers and factorial. Third part is where the benchmarking is actually performed. Bench.make_command takes a list of tests as its argumen. This is wrapped inside Command.run, which why we needed the Core module as well. Each test is created using Bench.Test.create function which takes a named argument, name, and an anonymous function wrapped inside parenthesis. The body of the function has ignore followed by our test function.

open Core
open Core_bench

let rec sumn n = if n = 0 then 0 else n + sumn (n-1)
let rec fact n = if n = 0 then 1 else n * fact (n-1)

let () =
  Command.run (Bench.make_command [
      Bench.Test.create ~name:"sumn"
        (fun () -> ignore (sumn 10));
      Bench.Test.create ~name:"fact"
        (fun () -> ignore (fact 10));])

When we compile and run the above code, each function will be run for 10 seconds and results will be displayed in a nice table in console:

Estimated testing time 20s (2 benchmarks x 10s). Change using '-quota'.
│ Name │ Time/Run │ Percentage │
│ sumn │  19.61ns │     77.80% │
│ fact │  25.20ns │    100.00% │

The table above shows that sumn took about 20 ns to finish while fact took about 25. Thus sumn ran about 22% faster than factorial.

This is just the bare minimum to get started. There is an interesting design discussion about the tool. Based on the memory consumed, it can also show columns with memory used in minor and major heaps, which is the concept used by OCaml's garbage collector.