Run this notebook

Use Livebook to open this notebook and explore new ideas.

It is easy to get started, on your machine or the cloud.

Click below to open and run it in your Livebook at .

(or change your Livebook location)

# BencheeDsl - A DSL for Benchee ```elixir Mix.install([ {:benchee_dsl, "~> 0.5"} ]) ``` ## Usage [BencheeDsl](https://hexdocs.pm/benchee_dsl/readme.html) offers a DSL to write benchmarks for [Benchee](https://github.com/bencheeorg/benchee) in an ExUnit style. For more informations to benchmarks and interpretation of the results see the [Benchee documentation](https://hexdocs.pm/benchee/readme.html). > Note: Currently, this notebook does not run with the Livebook App. ## Define a benchmark A benchmark is a module that uses `BenceeDsl.Benchmark`. The macro `job/2` defines the functions to benchmark. ```elixir defmodule Benchmark.One do use BencheeDsl.Benchmark @list Enum.to_list(1..10_000) defp map_fun(i), do: [i, i * i] job flat_map do Enum.flat_map(@list, &map_fun/1) end job map_flatten do @list |> Enum.map(&map_fun/1) |> List.flatten() end end ``` ## Run benchmark Benche runs the benchmark and writes the results to the console when the call 'Benchmark.One.run()' is made. See [Benchee/Features](https://github.com/bencheeorg/benchee#features) for a description of the different statistical values and what they mean. ```elixir Benchmark.One.run() ``` With the option `return: :result` the function `run/1` returns the `Benchee.Suite` struct with the results of the benchmark run. ```elixir Benchmark.One.run(return: :result) ``` ## Configuration Benchee takes a wealth of configuration options, however those are entirely optional. Benchee ships with sensible defaults for all of these, see [Benchee/Configuration](https://github.com/bencheeorg/benchee#configuration). The configuration is passed as a keyword list with `BencheeDsl`. ```elixir Benchmark.One.run(warmup: 1, time: 3, print: [configuration: false]) ``` The `config` macro can be used to directly write the configuration into the benchmark. ```elixir defmodule Benchmark.Two do use BencheeDsl.Benchmark config(warmup: 1, time: 3, print: [configuration: false]) @list Enum.to_list(1..10_000) defp map_fun(i), do: [i, i * i] job flat_map do Enum.flat_map(@list, &map_fun/1) end job map_flatten do @list |> Enum.map(&map_fun/1) |> List.flatten() end end Benchmark.Two.run() ``` ## Metrics to measure Benchee can't only measure [execution time](https://github.com/bencheeorg/benchee#measuring-time), but also [memory consumption](https://github.com/bencheeorg/benchee#measuring-memory-consumption) and [reductions](https://github.com/bencheeorg/benchee#measuring-reductions)! You can measure one of these metrics, or all at the same time. The choice is up to you. Warmup will only occur once though, the time for measuring the metrics are governed by time, memory_time and reduction_time configuration values respectively. By default only execution time is measured, memory and reductions need to be opted in by specifying a non 0 time amount. ```elixir Benchmark.Two.run(memory_time: 2, reduction_time: 2) ``` <!-- livebook:{"branch_parent_index":0} --> ## Inputs `:inputs` is a very useful configuration that allows you to run the same benchmarking jobs with different inputs. We call this combination a _"scenario"_. You specify the inputs as either a map from name (String or atom) to the actual input value or a list of tuples where the first element in each tuple is the name and the second element in the tuple is the value. Why do this? Functions can have different performance characteristics on differently shaped inputs - be that structure or input size. One of such cases is comparing tail-recursive and body-recursive implementations of `map`. More information in the [repository with the benchmark](https://github.com/PragTob/elixir_playground/blob/main/bench/tco_blog_post_focussed_inputs.exs) and the [blog post](https://pragtob.wordpress.com/2016/06/16/tail-call-optimization-in-elixir-erlang-not-as-efficient-and-important-as-you-probably-think/). ```elixir defmodule Benchmark.Three do use BencheeDsl.Benchmark defp map_fun(i), do: [i, i * i] job flat_map(input) do Enum.flat_map(input, &map_fun/1) end job map_flatten(input) do input |> Enum.map(&map_fun/1) |> List.flatten() end end inputs = %{ "Small" => Enum.to_list(1..1_000), "Medium" => Enum.to_list(1..10_000), "Bigger" => Enum.to_list(1..100_000) } Benchmark.Three.run(inputs: inputs, time: 3, pre_check: true) ``` The `inputs` macro can be used to directly write the inputs into the benchmark. ```elixir defmodule Benchmark.Four do use BencheeDsl.Benchmark config(time: 3, pre_check: true, print: [configuration: false]) inputs(%{ "Small" => Enum.to_list(1..1_000), "Medium" => Enum.to_list(1..10_000), "Bigger" => Enum.to_list(1..100_000) }) defp map_fun(i), do: [i, i * i] job flat_map(input) do Enum.flat_map(input, &map_fun/1) end job map_flatten(input) do input |> Enum.map(&map_fun/1) |> List.flatten() end end Benchmark.Four.run() ``` <!-- livebook:{"branch_parent_index":0} --> ## Capture a job The macro `job/1` accepts also a capture ([`&/1`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#&/1)) as argument. The benchmarked functions are written in a separate module. ```elixir defmodule Benchmark.Jobs do defp map_fun(i), do: [i, i * i] def flat_map(enum) do Enum.flat_map(enum, &map_fun/1) end def map_flatten(enum) do enum |> Enum.map(&map_fun/1) |> List.flatten() end end ``` The `inputs` in the benchmark module must now be a list of arguments. ```elixir defmodule Benchmark.Five do use BencheeDsl.Benchmark config(warmup: 1, time: 3, pre_check: true, print: [configuration: false]) inputs(%{ "Small" => [Enum.to_list(1..1_000)], "Big" => [Enum.to_list(1..100_000)] }) job(&Benchmark.Jobs.flat_map/1) job(&Benchmark.Jobs.map_flatten/1, as: "map and flat") end Benchmark.Five.run() ``` ## Capture all jobs The macro `jobs/1` generates from each public function of a given module a job. ```elixir defmodule Benchmark.Six do use BencheeDsl.Benchmark config(warmup: 1, time: 3) inputs(%{ "Small" => [Enum.to_list(1..1_000)], "Big" => [Enum.to_list(1..100_000)] }) jobs(Benchmark.Jobs) end Benchmark.Six.run() ``` <!-- livebook:{"branch_parent_index":0} --> ## Benchee smart cell `BencheeDsl` also brings the `Benchee` smart cell. <!-- livebook:{"attrs":{"source":"defmodule Benchmark do\n use BencheeDsl.Benchmark\n\n config warmup: 1, time: 1, pre_check: true\n\n inputs %{\n \"Small\" => Enum.to_list(1..1_000),\n \"Big\" => Enum.to_list(1..100_000)\n }\n\n defp map_fun(i), do: [i, i * i]\n\n job flat_map(input) do\n Enum.flat_map(input, &map_fun/1)\n end\n\n job map_flatten(input) do\n input |> Enum.map(&map_fun/1) |> List.flatten()\n end\nend"},"kind":"Elixir.BencheeDsl.SmartCell","livebook_object":"smart_cell"} --> ```elixir {:module, name, _binary, _bindings} = defmodule Benchmark do use BencheeDsl.Benchmark config(warmup: 1, time: 1, pre_check: true) inputs(%{"Small" => Enum.to_list(1..1000), "Big" => Enum.to_list(1..100_000)}) defp map_fun(i) do [i, i * i] end job(flat_map(input)) do Enum.flat_map(input, &map_fun/1) end job(map_flatten(input)) do input |> Enum.map(&map_fun/1) |> List.flatten() end end BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render() ``` <!-- livebook:{"branch_parent_index":0} --> ## Benchee style If you have read everything up to here and still don't want to have a DSL, then `Benchee` alone will do. ```elixir defmodule MyMap do def flat_map(input) do Enum.flat_map(input, &map_fun/1) end def map_flatten(input) do input |> Enum.map(&map_fun/1) |> List.flatten() end defp map_fun(i), do: [i, i * i] end Benchee.run( %{ "flat_map" => &MyMap.flat_map/1, "map.flatten" => &MyMap.map_flatten/1 }, warmup: 1, time: 3, inputs: %{ "Small" => Enum.to_list(1..1_000), "Big" => Enum.to_list(1..100_000) } ) ``` This example shows also that we get the "same" results as in the other examples.
See source

Have you already installed Livebook?

If you already installed Livebook, you can configure the default Livebook location where you want to open notebooks.
Livebook up Checking status We can't reach this Livebook (but we saved your preference anyway)
Run notebook

Not yet? Install Livebook in just a minute

Livebook is open source, free, and ready to run anywhere.

Run on your machine

with Livebook Desktop

Run in the cloud

on select platforms

To run on Linux, Docker, embedded devices, or Elixir’s Mix, check our README.

PLATINUM SPONSORS
SPONSORS
Code navigation with go to definition of modules and functions Read More ×