Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project benchmarks #312

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ doc
docs
/test/tmp
save.benchee
/.elixir_ls/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ The available options are the following (also documented in [hexdocs](https://he
* `print` - a map or keyword list from atoms to `true` or `false` to configure if the output identified by the atom will be printed during the standard benchee benchmarking process. All options are enabled by default (true). Options are:
* `:benchmarking` - print when Benchee starts benchmarking a new job (`Benchmarking name ...`)
* `:configuration` - a summary of configured benchmarking options including estimated total run time is printed before benchmarking starts
* `:system` - a description of a system benchmark is being run on
* `:fast_warning` - warnings are displayed if functions are executed too fast leading to inaccurate measures
* `:unit_scaling` - the strategy for choosing a unit for durations,
counts & memory measurements. May or may not be implemented by a given formatter (The console formatter implements it).
Expand Down
87 changes: 87 additions & 0 deletions example/project_benchmark.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
defmodule Clickhouse.Benchmark.Performance do
use Benchee.Benchmark

# Since it's a module, any other constructs may be used,
# such as defs, defmacro, etc.
def map_fun(i) do
[i]
end

# Global benchmark setup. Optional.
#
# Called before any benchmark in the module.
#
# If defined, must return {:ok, state} for benchmarks to run
before_all do
{:ok, nil}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need {:ok, state} ? I know ExUnit does it, but why can't we just return state ? If something goes wrong it can just raise or what am I missing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's rather a matter of convention. raise means some serious runtime error that should have never happened if the code were write.
Surely, we can, just throw things, but it will be more implicit I think: if you just look on the result type of the setup, you already know what it should return to work correctly. To tell programmers to just throw their error would be less obvious IMO than that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just never quite understood it. In what scenario will my setup not work? Usually the code I call will already raise at some point. It might just be me, I've never seen setup implementation that returns anything but {:ok, state}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the code that returns ok/error pair usually should never throw (although might raise), at least with any other reason then MatchError, but that's on the caller side, so not function's problem, but programmer that's calling it :)

Surely it might be easier to write happy-path-code all the way, just a matter of taste I guess.

Share your final thoughts on this :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with it for ExUnits sake 🤷‍♂️

end

# Global benchmark teardown. Optional.
#
# Called after all benchmarks have finished
# (and their possible local teardowns had been called).
#
# Can return anything.
after_all do
:anything
end

# Benchmarks. Module may have many of them.
benchmark "Flattening list from map", # Name. Mandatory.
warmup: 0, time: 1 # Opts (see Benchee.run). Optional.
jrogov marked this conversation as resolved.
Show resolved Hide resolved
do # Do block. Mandatory.

# Benchmark setup. Optional
#
# If global setup is defined,
# implicit variable "state" is bound to it's result
#
# If defined, must return {:ok, state} for benchmarks to run
before_benchmark do
{:ok, fn x -> [x] end}
end

# Benchmark teardown. Optional
#
# Implicit variable: state, as it is returned from local setup
# (or from global setup, if no local setup is defined)
#
# Can return anything
after_benchmark do
:anything
end

# Inputs: the same as passing inputs via :input option
#
# Accepts either an expression or a do block as a 2nd argument
input "Small", Enum.to_list(1..100)
input "Medium" do
n = 10_000
Enum.to_list(1..n)
end
input "Bigger", Enum.to_list(1..100_000)
jrogov marked this conversation as resolved.
Show resolved Hide resolved


# Benchmark scenarios
#
# Each scenario has an implicit variables:
# - state: state returned from local or global setup
# And if any input is passed (either with option or as input directive):
# - input: data for benchmark
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I like the idea of implicit variables.

I'd like it to be more explicit. How about doing it like ExUnit where you pattern match them from a map that was returned by setup?

Not sure what to do about input. I tend to want to pass it along explicitly. It just magically being there, feels kinda weird in elixir. Not sure though.

Copy link
Author

@jrogov jrogov Dec 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I was gritting my teeth when wrote this part. Although I somewhat despise magic forced implicits and implicit variables, I must admit that with implicits benchmark code looks cleaner.

Nonetheless, if you can give me examples of parameters/variables that we might be adding in the future, I will happily change implicits to map.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the map I just meant how you can reuse what setup returned in the tests: https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html#setup_all/1-examples

Note sure what we'd need the examples for, overall, this would give people the possibility to put in there whatever data they need.

Not sure if that's the best. In "normal" benchee it's solved through just giving the function an argument where those values can be accomulated was the solution. Not certain one could do to give do blcoks an argument but I think I'd prefer it over implictness :)

scenario "Enum.flat_map", # Name. Mandatory
before_scenario:
fn i -> # Scenario options, e.g. local hooks. Optional.
IO.inspect(length(i), label: "Input length");
i
end
do # Do block. Mandatory
map_fun = state
Enum.flat_map(input, map_fun)
end

scenario "Enum.map |> List.flatten" do
map_fun = state
input |> Enum.map(map_fun) |> List.flatten()
end
end
end
13 changes: 13 additions & 0 deletions lib/benchee/benchmark.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,22 @@ defmodule Benchee.Benchmark do
printer \\ Printer,
runner \\ Runner
) do
printer.system_information(suite)
printer.configuration_information(suite)
scenario_context = %ScenarioContext{config: config, printer: printer}
scenarios = runner.run_scenarios(scenarios, scenario_context)
%Suite{suite | scenarios: scenarios}
end

# delegate use to Benchee.Project.Benchmark.__using__/1
defmacro __using__(opts) do
require Benchee.Project.Benchmark

quoted =
quote do
use Benchee.Project.Benchmark, unquote(opts)
end

Macro.expand(quoted, __ENV__)
end
end
15 changes: 10 additions & 5 deletions lib/benchee/configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ defmodule Benchee.Configuration do
percentiles: [50, 99],
print: %{
benchmarking: true,
fast_warning: true,
configuration: true,
fast_warning: true
system: true
},
inputs: nil,
save: false,
Expand Down Expand Up @@ -178,7 +179,8 @@ defmodule Benchee.Configuration do
print: %{
benchmarking: true,
fast_warning: true,
configuration: true
configuration: true,
system: true
},
percentiles: [50, 99],
unit_scaling: :best,
Expand Down Expand Up @@ -206,7 +208,8 @@ defmodule Benchee.Configuration do
print: %{
benchmarking: true,
fast_warning: true,
configuration: true
configuration: true,
system: true
},
percentiles: [50, 99],
unit_scaling: :best,
Expand Down Expand Up @@ -234,7 +237,8 @@ defmodule Benchee.Configuration do
print: %{
benchmarking: true,
fast_warning: true,
configuration: true
configuration: true,
system: true
},
percentiles: [50, 99],
unit_scaling: :best,
Expand Down Expand Up @@ -269,7 +273,8 @@ defmodule Benchee.Configuration do
print: %{
benchmarking: true,
fast_warning: false,
configuration: true
configuration: true,
system: true
},
percentiles: [50, 99],
unit_scaling: :smallest,
Expand Down
25 changes: 17 additions & 8 deletions lib/benchee/output/benchmark_printer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,17 @@ defmodule Benchee.Output.BenchmarkPrinter do
end

@doc """
Prints general information such as system information and estimated
benchmarking time.
Prints general information about the system such as operating system.
"""
def configuration_information(%{configuration: %{print: %{configuration: false}}}) do
def system_information(%{configuration: %{print: %{system: false}}}) do
nil
end

def configuration_information(%{scenarios: scenarios, system: sys, configuration: config}) do
system_information(sys)
suite_information(scenarios, config)
def system_information(%{system: sys}) do
print_system_information(sys)
end

defp system_information(%{
defp print_system_information(%{
erlang: erlang_version,
elixir: elixir_version,
os: os,
Expand All @@ -45,7 +43,18 @@ defmodule Benchee.Output.BenchmarkPrinter do
""")
end

defp suite_information(scenarios, %{
@doc """
Prints general benchmark information such as estimated benchmarking time.
"""
def configuration_information(%{configuration: %{print: %{configuration: false}}}) do
nil
end

def configuration_information(%{scenarios: scenarios, configuration: config}) do
print_suite_information(scenarios, config)
end

defp print_suite_information(scenarios, %{
parallel: parallel,
time: time,
warmup: warmup,
Expand Down
Loading