Packages

A tool to run benchmarks to determine iteration per second.

Current section

Files

Jump to
benchmark_ips lib benchmark_ips.ex
Raw

lib/benchmark_ips.ex

defmodule BenchmarkIps do
@typep zero_arity_fun :: (() -> any)
@doc """
Returns current time in microseconds
"""
@spec now_us() :: pos_integer
def now_us do
:erlang.monotonic_time(:micro_seconds)
end
@doc """
Calls the function the specified number of times
"""
@spec call_times(zero_arity_fun, pos_integer) :: nil
def call_times(fun, times) do
fun.()
if times > 1 do
call_times(fun, times - 1)
end
end
@doc """
Returns the result of running a function and the time it took to run in microseconds
"""
@spec measure_in_us(zero_arity_fun) :: {any, number}
def measure_in_us(fun) do
start = now_us
result = fun.()
finish = now_us
{result, finish - start}
end
@doc """
Returns the number of iterations that should be able to be run in 100ms given a number
of iterations and a duration in microseconds
"""
@spec iterations_per_100ms(pos_integer, number) :: pos_integer
def iterations_per_100ms(iters, time_us) do
time_ms = time_us * 1.0e-3
iterations_per_ms = iters / time_ms
batches = iterations_per_ms * 100
batches = round(batches)
if batches <= 0 do
1
else
batches
end
end
@doc """
Returns the number of iterations a function can run in the provided duration (microseconds) by
running it in batches and checking between batches on the time elapsed
"""
@spec iterations_runnable_in_duration_by_batches(zero_arity_fun, pos_integer, number) :: pos_integer
def iterations_runnable_in_duration_by_batches(fun, batches, target_duration_us) do
iterations_runnable_in_duration_by_batches(fun, batches, now_us + target_duration_us, 0)
end
def iterations_runnable_in_duration_by_batches(fun, batches, target_end_time_us, iters) do
if now_us < target_end_time_us do
call_times(fun, batches)
iterations_runnable_in_duration_by_batches(fun, batches, target_end_time_us, iters + 1)
else
iters * batches
end
end
@doc """
Returns the number of iterations and elapsed time of running a function in batches for the
provided target duration (seconds)
"""
@spec bench(zero_arity_fun, pos_integer, number) :: {pos_integer, number}
def bench(fun, batches, target_duration_s) do
target_duration_us = target_duration_s * 1.0e6
{iters, actual_time_us} = measure_in_us(fn ->
iterations_runnable_in_duration_by_batches(fun, batches, target_duration_us)
end)
{iters, actual_time_us}
end
@doc """
Benchmarks a function and returns the iterations per second and microseconds per iterations
The function is first benchmarked for the provided warmup duration (seconds, default 1) in
batches of 1 in order to determine about how many iterations can run in 100ms.
It is then benchmarked in batches of that number of iterations for the provided bench duration
(seconds, default 2).
"""
@spec report(zero_arity_fun, [warmup_duration_s: number, bench_duration_s: number]) :: {float, float}
def report(fun, options \\ []) do
warmup_duration_s = Keyword.get(options, :warmup_duration_s, 1)
bench_duration_s = Keyword.get(options, :bench_duration_s, 2)
{warmup_iters, warmup_time_us} = bench(fun, 1, warmup_duration_s)
warmup_iterations_per_100ms = iterations_per_100ms(warmup_iters, warmup_time_us)
{actual_iters, actual_time_us} = bench(fun, warmup_iterations_per_100ms, bench_duration_s)
iters_per_us = actual_iters / actual_time_us
us_per_iter = 1 / iters_per_us
iters_per_s = iters_per_us * 1.0e6
{iters_per_s, us_per_iter}
end
end