Using Mix.install for Benchmarks

In a previous post, I covered using the recently released function, Mix.install. In this post, I cover how to use Mix.install for benchmarking.

If you are interested in (micro) benchmarking, you may already be familiar with benchee. Benchee provides the ability to collect and compare metrics about speed (via iterations per second) and memory usage for your function calls.

Because benchee is a third-party library, we have to install it via Hex. While benchee is useful enough to include it as a dev-dependency in your project, you may no longer have to with Mix.install.

Let's take a look at an example stolen copied directly from the benchee README

# my_test_benchmark.exs

Mix.install([:benchee])

list = Enum.to_list(1..10_000)
map_fun = fn i -> [i, i * i] end

Benchee.run(%{
  "flat_map" => fn -> Enum.flat_map(list, map_fun) end,
  "map.flatten" => fn -> list |> Enum.map(map_fun) |> List.flatten() end
})

With the addition of Mix.install([:benchee]), Elixir will fetch and build our benchee dependency on the first run.

› elixir my_test_benchmark.exs

Resolving Hex dependencies...
Dependency resolution completed:
New:
  benchee 1.0.1
  deep_merge 1.0.0
* Getting benchee (Hex package)
* Getting deep_merge (Hex package)
==> deep_merge
Compiling 2 files (.ex)
Generated deep_merge app
==> benchee
Compiling 39 files (.ex)
Generated benchee app

Once this is complete, we will see our benchee's benchmark results.

Operating System: macOS
CPU Information: Intel(R) Core(TM) i7-4771 CPU @ 3.50GHz
Number of Available Cores: 8
Available memory: 32 GB
Elixir 1.12.3
Erlang 24.1.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 14 s

Benchmarking flat_map...
Benchmarking map.flatten...

Name                  ips        average  deviation         median         99th %
flat_map           2.50 K      400.75 μs    ±19.69%         378 μs      826.49 μs
map.flatten        1.24 K      806.81 μs    ±35.19%         658 μs        1625 μs

Comparison:
flat_map           2.50 K
map.flatten        1.24 K - 2.01x slower +406.05 μs

Because Mix.install caches our dependencies, subsequent runs will not need to rebuild the dependencies and immediately start the benchmark.

We now have a single file benchmark script that we can easily re-run and share with others.

A word of warning: a limitation of this approach is that you do not get access to your application code like you would when running a script via mix run. It may be a bit of a stretch, but this may be a feature and not a bug. By extracting the core functionality you want to benchmark away from the rest of your application, you may better avoid the noise and side effects that could come as a part of running other parts of your application.


Notice something wrong? Please consider proposing an edit or opening an issue.