Skip to content

Instantly share code, notes, and snippets.

@potatosalad
Created September 2, 2018 17:13
Show Gist options
  • Save potatosalad/985b8dfabf0563a343e158635562a866 to your computer and use it in GitHub Desktop.
Save potatosalad/985b8dfabf0563a343e158635562a866 to your computer and use it in GitHub Desktop.
%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*-
%% vim: ts=4 sw=4 ft=erlang noet
-module(quickbench).
%% API
-export([bench/2]).
-export([bench/3]).
-export([compare/3]).
%% Records
-record(stat, {
acc = 0 :: non_neg_integer(),
min = 0 :: non_neg_integer(),
max = 0 :: non_neg_integer()
}).
%% Types
-type arguments_list(Type) :: [Type].
-type arguments_function(Type) ::
fun(() -> arguments_list(Type)).
-type arguments(Type) :: arguments_function(Type) | arguments_list(Type).
-type arguments() :: arguments(term()).
-export_type([arguments/1]).
-export_type([arguments/0]).
-type metric() :: #{
acc := non_neg_integer(),
avg := float(),
min := non_neg_integer(),
max := non_neg_integer()
}.
-export_type([metric/0]).
-type stats() :: #{
reds := metric(),
time := metric()
}.
-export_type([stats/0]).
%%====================================================================
%% API
%%====================================================================
-spec bench(function(), arguments()) -> stats().
bench(Function, Arguments) ->
bench(Function, Arguments, 1).
-spec bench(function(), arguments(), non_neg_integer()) -> stats().
bench(Function, Arguments, N)
when is_function(Function)
andalso (is_list(Arguments) orelse is_function(Arguments, 0))
andalso (is_integer(N) andalso N > 0) ->
{Time, Reds} = bench_loop(N, erlang:self(), #stat{}, #stat{}, Function, Arguments),
#{
time => stat_final(Time, N),
reds => stat_final(Reds, N)
}.
-spec compare([{atom(), function()}], arguments(), non_neg_integer()) -> [{atom(), stats()}].
compare(Groups, Arguments, N)
when is_list(Groups)
andalso (is_list(Arguments) orelse is_function(Arguments, 0))
andalso (is_integer(N) andalso N > 0) ->
ResolvedArguments = resolve(Arguments),
[begin
{Label, bench(Function, ResolvedArguments, N)}
end || {Label, Function} <- Groups, is_atom(Label) andalso is_function(Function)].
%%%-------------------------------------------------------------------
%%% Internal functions
%%%-------------------------------------------------------------------
%% @private
bench_loop(0, _Self, Time, Reds, _Function, _Arguments) ->
{Time, Reds};
bench_loop(I, Self, Time0, Reds0, Function, Arguments) ->
Args = resolve(Arguments),
T1 = erlang:monotonic_time(),
{reductions, R1} = erlang:process_info(Self, reductions),
_ = erlang:apply(Function, Args),
{reductions, R2} = erlang:process_info(Self, reductions),
T2 = erlang:monotonic_time(),
Time1 = stat_update(Time0, T2 - T1),
Reds1 = stat_update(Reds0, R2 - R1),
bench_loop(I - 1, Self, Time1, Reds1, Function, Arguments).
%% @private
resolve(Arguments) when is_function(Arguments, 0) ->
resolve(Arguments());
resolve(Arguments) when is_list(Arguments) ->
Arguments.
%% @private
stat_final(#stat{ acc = Acc, min = Min, max = Max }, N) ->
#{
acc => Acc,
avg => (Acc / N),
min => Min,
max => Max
}.
%% @private
stat_update(Stat = #stat{ acc = Acc, min = Min, max = Max }, Val) ->
Stat#stat{
acc = Acc + Val,
min =
case Val < Min of
_ when Min =:= 0 ->
Val;
true ->
Val;
false ->
Min
end,
max =
case Val > Max of
true ->
Val;
false ->
Max
end
}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment