Current section

Files

Jump to
gleamy_bench src gleamy_bench.erl
Raw

src/gleamy_bench.erl

-module(gleamy_bench).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
-export([run/3, do_repeat/3, repeat/2, table/2]).
-export_type([input/1, function_/2, set/0, stat/0, option/0, options/0]).
-type input(FGK) :: {input, binary(), FGK}.
-type function_(FGL, FGM) :: {function, binary(), fun((FGL) -> FGM)}.
-type set() :: {set, binary(), binary(), list(float())}.
-type stat() :: {p, integer()} |
ips |
min |
max |
mean |
sd |
sd_percent |
{stat, binary(), fun((set()) -> float())}.
-type option() :: {warmup, integer()} | {duration, integer()} | quiet.
-type options() :: {options, integer(), integer(), boolean()}.
-spec now() -> float().
now() ->
Ns = os:perf_counter(1000000000),
gleam@int:to_float(Ns) / 1000000.0.
-spec mean(list(float())) -> float().
mean(Data) ->
Count = gleam@int:to_float(gleam@list:length(Data)),
case Count of
0.0 -> 0.0;
Gleam@denominator -> gleam@float:sum(Data) / Gleam@denominator
end.
-spec standard_deviation(list(float())) -> float().
standard_deviation(Data) ->
Count = gleam@int:to_float(gleam@list:length(Data)),
Mean = mean(Data),
_assert_subject = begin
_pipe = (case Count of
0.0 -> 0.0;
Gleam@denominator -> gleam@float:sum(
gleam@list:map(
Data,
fun(X) ->
Y = X - Mean,
Y * Y
end
)
)
/ Gleam@denominator
end),
gleam@float:square_root(_pipe)
end,
{ok, Value} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gleamy_bench"/utf8>>,
function => <<"standard_deviation"/utf8>>,
line => 50})
end,
Value.
-spec min(list(float())) -> float().
min(Data) ->
First = case Data of
[X | _] ->
X;
_ ->
+0.0
end,
gleam@list:fold(Data, First, fun(A, X@1) -> gleam@float:min(A, X@1) end).
-spec percentile(integer(), list(float())) -> float().
percentile(N, Data) ->
Data@1 = begin
_pipe = Data,
_pipe@1 = gleam@list:sort(_pipe, fun gleam@float:compare/2),
gleam@list:drop(_pipe@1, (N * gleam@list:length(Data)) div 100)
end,
case Data@1 of
[X | _] ->
X;
_ ->
+0.0
end.
-spec max(list(float())) -> float().
max(Data) ->
First = case Data of
[X | _] ->
X;
_ ->
+0.0
end,
gleam@list:fold(Data, First, fun(A, X@1) -> gleam@float:max(A, X@1) end).
-spec do_repeat_until(list(float()), float(), FGU, fun((FGU) -> any())) -> list(float()).
do_repeat_until(Acc, Stop, Value, Fun) ->
Start = now(),
_ = Fun(Value),
End = now(),
case End of
_ when End < Stop ->
do_repeat_until([End - Start | Acc], Stop, Value, Fun);
_ ->
Acc
end.
-spec repeat_until(float(), FGX, fun((FGX) -> any())) -> list(float()).
repeat_until(Duration, Value, Fun) ->
do_repeat_until([], now() + Duration, Value, Fun).
-spec default_options() -> options().
default_options() ->
{options, 50, 500, false}.
-spec apply_options(options(), list(option())) -> options().
apply_options(Default, Options) ->
case Options of
[] ->
Default;
[X | Xs] ->
case X of
{warmup, Ms} ->
apply_options(erlang:setelement(2, Default, Ms), Xs);
{duration, Ms@1} ->
apply_options(erlang:setelement(3, Default, Ms@1), Xs);
quiet ->
apply_options(erlang:setelement(4, Default, true), Xs)
end
end.
-spec run(list(input(FHB)), list(function_(FHB, any())), list(option())) -> list(set()).
run(Inputs, Functions, Options) ->
Options@1 = apply_options(default_options(), Options),
gleam@list:flat_map(
Inputs,
fun(_use0) ->
{input, Input_label, Input} = _use0,
gleam@list:map(Functions, fun(Function) -> case Function of
{function, Fun_label, Fun} ->
case erlang:element(4, Options@1) of
true ->
nil;
false ->
gleam@io:println(
<<<<<<"benching set "/utf8,
Input_label/binary>>/binary,
" "/utf8>>/binary,
Fun_label/binary>>
)
end,
_ = repeat_until(
gleam@int:to_float(erlang:element(2, Options@1)),
Input,
Fun
),
Timings = repeat_until(
gleam@int:to_float(erlang:element(3, Options@1)),
Input,
Fun
),
{set, Input_label, Fun_label, Timings}
end end)
end
).
-spec do_repeat(integer(), FHK, fun((FHK) -> any())) -> nil.
do_repeat(N, Input, Fun) ->
case N of
0 ->
nil;
_ ->
_ = Fun(Input),
do_repeat(N - 1, Input, Fun)
end.
-spec repeat(integer(), fun((FHN) -> any())) -> fun((FHN) -> nil).
repeat(N, Fun) ->
fun(Input) -> do_repeat(N, Input, Fun) end.
-spec format_float(float(), integer()) -> binary().
format_float(F, Decimals) ->
_assert_subject = gleam@int:power(10, gleam@int:to_float(Decimals)),
{ok, Factor} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gleamy_bench"/utf8>>,
function => <<"format_float"/utf8>>,
line => 180})
end,
Whole = gleam@float:truncate(F),
Decimal = gleam@float:truncate(F * Factor) - (Whole * gleam@float:truncate(
Factor
)),
gleam@string:concat(
[gleam@string:pad_left(
gleam@int:to_string(Whole),
(14 - Decimals) - 1,
<<" "/utf8>>
),
<<"."/utf8>>,
gleam@string:pad_left(
gleam@int:to_string(Decimal),
Decimals,
<<"0"/utf8>>
)]
).
-spec header_row(list(stat())) -> binary().
header_row(Stats) ->
_pipe = [gleam@string:pad_right(<<"Input"/utf8>>, 20, <<" "/utf8>>),
gleam@string:pad_right(<<"Function"/utf8>>, 20, <<" "/utf8>>) |
gleam@list:map(
Stats,
fun(Stat) ->
Stat@1 = case Stat of
{p, N} ->
<<"P"/utf8, (gleam@int:to_string(N))/binary>>;
ips ->
<<"IPS"/utf8>>;
min ->
<<"Min"/utf8>>;
max ->
<<"Max"/utf8>>;
mean ->
<<"Mean"/utf8>>;
sd ->
<<"SD"/utf8>>;
sd_percent ->
<<"SD%"/utf8>>;
{stat, Name, _} ->
Name
end,
gleam@string:pad_left(Stat@1, 14, <<" "/utf8>>)
end
)],
gleam@string:join(_pipe, <<"\t"/utf8>>).
-spec stat_row(set(), list(stat())) -> binary().
stat_row(Set, Stats) ->
_pipe@2 = [gleam@string:pad_right(erlang:element(2, Set), 20, <<" "/utf8>>),
gleam@string:pad_right(erlang:element(3, Set), 20, <<" "/utf8>>) |
gleam@list:map(
Stats,
fun(Stat) ->
Stat@1 = case Stat of
{p, N} ->
percentile(N, erlang:element(4, Set));
ips ->
case gleam@float:sum(erlang:element(4, Set)) of
0.0 -> 0.0;
Gleam@denominator -> (1000.0 * gleam@int:to_float(
gleam@list:length(erlang:element(4, Set))
))
/ Gleam@denominator
end;
min ->
min(erlang:element(4, Set));
max ->
max(erlang:element(4, Set));
mean ->
mean(erlang:element(4, Set));
sd ->
standard_deviation(erlang:element(4, Set));
sd_percent ->
case mean(erlang:element(4, Set)) of
0.0 -> 0.0;
Gleam@denominator@1 -> (100.0 * standard_deviation(
erlang:element(4, Set)
))
/ Gleam@denominator@1
end;
{stat, _, Calc} ->
Calc(Set)
end,
_pipe = Stat@1,
_pipe@1 = format_float(_pipe, 4),
gleam@string:pad_left(_pipe@1, 14, <<" "/utf8>>)
end
)],
gleam@string:join(_pipe@2, <<"\t"/utf8>>).
-spec table(list(set()), list(stat())) -> binary().
table(Sets, Stats) ->
Header = header_row(Stats),
Body = gleam@list:map(Sets, fun(_capture) -> stat_row(_capture, Stats) end),
_pipe = [Header | Body],
gleam@string:join(_pipe, <<"\n"/utf8>>).