Current section
Files
Jump to
Current section
Files
src/bench@bench.erl
-module(bench@bench).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/bench/bench.gleam").
-export([default_config/0, quick_config/0, thorough_config/0, config/3, new_suite/1, add_result/2, benchmark_with_config/3, benchmark/2, benchmark_detailed/3, benchmark_with_setup/5, to_markdown/1, print_suite_summary/1, print_result/1, run_suite/3, compare_results/2, within_threshold/3]).
-export_type([bench_config/0, bench_result/0, bench_suite/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type bench_config() :: {bench_config, integer(), integer(), boolean()}.
-type bench_result() :: {bench_result,
binary(),
integer(),
integer(),
float(),
gleam@option:option(float()),
gleam@option:option(float())}.
-type bench_suite() :: {bench_suite, binary(), list(bench_result())}.
-file("src/bench/bench.gleam", 58).
?DOC(" Default benchmark configuration\n").
-spec default_config() -> bench_config().
default_config() ->
{bench_config, 10, 1000, false}.
-file("src/bench/bench.gleam", 63).
?DOC(" Quick benchmark configuration (fewer iterations)\n").
-spec quick_config() -> bench_config().
quick_config() ->
{bench_config, 5, 100, false}.
-file("src/bench/bench.gleam", 68).
?DOC(" Thorough benchmark configuration (more iterations)\n").
-spec thorough_config() -> bench_config().
thorough_config() ->
{bench_config, 100, 10000, false}.
-file("src/bench/bench.gleam", 73).
?DOC(" Create a custom configuration\n").
-spec config(integer(), integer(), boolean()) -> bench_config().
config(Warmup_iterations, Iterations, Verbose) ->
{bench_config, Warmup_iterations, Iterations, Verbose}.
-file("src/bench/bench.gleam", 190).
?DOC(" Create a new benchmark suite\n").
-spec new_suite(binary()) -> bench_suite().
new_suite(Name) ->
{bench_suite, Name, []}.
-file("src/bench/bench.gleam", 195).
?DOC(" Add a benchmark result to a suite\n").
-spec add_result(bench_suite(), bench_result()) -> bench_suite().
add_result(Suite, Result) ->
{bench_suite,
erlang:element(2, Suite),
lists:append(erlang:element(3, Suite), [Result])}.
-file("src/bench/bench.gleam", 314).
?DOC(" Run a function n times\n").
-spec run_iterations(integer(), fun(() -> any())) -> nil.
run_iterations(N, F) ->
case N > 0 of
false ->
nil;
true ->
_ = F(),
run_iterations(N - 1, F)
end.
-file("src/bench/bench.gleam", 91).
?DOC(" Run a benchmark with custom configuration\n").
-spec benchmark_with_config(binary(), bench_config(), fun(() -> any())) -> bench_result().
benchmark_with_config(Name, Config, F) ->
run_iterations(erlang:element(2, Config), F),
Start = bench_ffi:monotonic_time_us(),
run_iterations(erlang:element(3, Config), F),
End = bench_ffi:monotonic_time_us(),
Total_us = End - Start,
Total_ms = Total_us div 1000,
Avg_us = case erlang:float(erlang:element(3, Config)) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:float(Total_us) / Gleam@denominator
end,
{bench_result,
Name,
Total_ms,
erlang:element(3, Config),
Avg_us,
none,
none}.
-file("src/bench/bench.gleam", 86).
?DOC(" Run a benchmark with default configuration\n").
-spec benchmark(binary(), fun(() -> any())) -> bench_result().
benchmark(Name, F) ->
benchmark_with_config(Name, default_config(), F).
-file("src/bench/bench.gleam", 325).
?DOC(" Measure each iteration individually\n").
-spec measure_each_iteration(integer(), fun(() -> any()), list(integer())) -> list(integer()).
measure_each_iteration(N, F, Times) ->
case N > 0 of
false ->
lists:reverse(Times);
true ->
Start = bench_ffi:monotonic_time_us(),
_ = F(),
End = bench_ffi:monotonic_time_us(),
measure_each_iteration(N - 1, F, [End - Start | Times])
end.
-file("src/bench/bench.gleam", 119).
?DOC(" Run a benchmark capturing min/max times (slower but more detailed)\n").
-spec benchmark_detailed(binary(), bench_config(), fun(() -> any())) -> bench_result().
benchmark_detailed(Name, Config, F) ->
run_iterations(erlang:element(2, Config), F),
Times = measure_each_iteration(erlang:element(3, Config), F, []),
Total_us = gleam@list:fold(Times, 0, fun gleam@int:add/2),
Total_ms = Total_us div 1000,
Avg_us = case erlang:float(erlang:element(3, Config)) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:float(Total_us) / Gleam@denominator
end,
Min_us = begin
_pipe = Times,
_pipe@1 = gleam@list:reduce(_pipe, fun gleam@int:min/2),
_pipe@2 = gleam@result:map(_pipe@1, fun erlang:float/1),
gleam@option:from_result(_pipe@2)
end,
Max_us = begin
_pipe@3 = Times,
_pipe@4 = gleam@list:reduce(_pipe@3, fun gleam@int:max/2),
_pipe@5 = gleam@result:map(_pipe@4, fun erlang:float/1),
gleam@option:from_result(_pipe@5)
end,
{bench_result,
Name,
Total_ms,
erlang:element(3, Config),
Avg_us,
Min_us,
Max_us}.
-file("src/bench/bench.gleam", 338).
?DOC(" Run iterations with setup/teardown\n").
-spec run_iterations_with_setup(
integer(),
fun(() -> KYH),
fun((KYH) -> KYI),
fun((KYH, KYI) -> nil)
) -> nil.
run_iterations_with_setup(N, Setup, F, Teardown) ->
case N > 0 of
false ->
nil;
true ->
State = Setup(),
Result = F(State),
Teardown(State, Result),
run_iterations_with_setup(N - 1, Setup, F, Teardown)
end.
-file("src/bench/bench.gleam", 156).
?DOC(" Run a function that needs setup/teardown for each iteration\n").
-spec benchmark_with_setup(
binary(),
bench_config(),
fun(() -> KXZ),
fun((KXZ) -> KYA),
fun((KXZ, KYA) -> nil)
) -> bench_result().
benchmark_with_setup(Name, Config, Setup, F, Teardown) ->
run_iterations_with_setup(erlang:element(2, Config), Setup, F, Teardown),
Start = bench_ffi:monotonic_time_us(),
run_iterations_with_setup(erlang:element(3, Config), Setup, F, Teardown),
End = bench_ffi:monotonic_time_us(),
Total_us = End - Start,
Total_ms = Total_us div 1000,
Avg_us = case erlang:float(erlang:element(3, Config)) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:float(Total_us) / Gleam@denominator
end,
{bench_result,
Name,
Total_ms,
erlang:element(3, Config),
Avg_us,
none,
none}.
-file("src/bench/bench.gleam", 380).
-spec power_of_10(integer()) -> float().
power_of_10(N) ->
case N of
0 ->
1.0;
1 ->
10.0;
2 ->
100.0;
3 ->
1000.0;
_ ->
10.0 * power_of_10(N - 1)
end.
-file("src/bench/bench.gleam", 368).
?DOC(" Convert float to string with limited precision\n").
-spec float_to_string_precision(float(), integer()) -> binary().
float_to_string_precision(F, Precision) ->
Multiplier = power_of_10(Precision),
Rounded = case Multiplier of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:float(erlang:round(F * Multiplier)) / Gleam@denominator
end,
Str = gleam_stdlib:float_to_string(Rounded),
case gleam_stdlib:contains_string(Str, <<"."/utf8>>) of
true ->
Str;
false ->
<<Str/binary, ".0"/utf8>>
end.
-file("src/bench/bench.gleam", 356).
?DOC(" Format microseconds as a human-readable duration\n").
-spec format_duration_us(float()) -> binary().
format_duration_us(Us) ->
case Us < 1000.0 of
true ->
<<(float_to_string_precision(Us, 2))/binary, "us"/utf8>>;
false ->
case Us < 1000000.0 of
true ->
<<(float_to_string_precision(Us / 1000.0, 2))/binary,
"ms"/utf8>>;
false ->
<<(float_to_string_precision(Us / 1000000.0, 2))/binary,
"s"/utf8>>
end
end.
-file("src/bench/bench.gleam", 284).
?DOC(" Format a benchmark suite as markdown table\n").
-spec to_markdown(bench_suite()) -> binary().
to_markdown(Suite) ->
Header = <<"| Benchmark | Avg/Op | Total | Iterations |\n"/utf8>>,
Separator = <<"|-----------|--------|-------|------------|\n"/utf8>>,
Rows = begin
_pipe = gleam@list:map(
erlang:element(3, Suite),
fun(Result) ->
<<<<<<<<<<<<<<<<"| "/utf8, (erlang:element(2, Result))/binary>>/binary,
" | "/utf8>>/binary,
(format_duration_us(
erlang:element(5, Result)
))/binary>>/binary,
" | "/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(3, Result)
))/binary>>/binary,
"ms | "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(4, Result)))/binary>>/binary,
" |\n"/utf8>>
end
),
erlang:list_to_binary(_pipe)
end,
<<<<<<<<<<"### "/utf8, (erlang:element(2, Suite))/binary>>/binary,
"\n\n"/utf8>>/binary,
Header/binary>>/binary,
Separator/binary>>/binary,
Rows/binary>>.
-file("src/bench/bench.gleam", 391).
?DOC(" Pad a string on the right to a given length\n").
-spec pad_right(binary(), integer()) -> binary().
pad_right(S, Len) ->
Current_len = string:length(S),
case Current_len >= Len of
true ->
S;
false ->
<<S/binary,
(gleam@string:repeat(<<" "/utf8>>, Len - Current_len))/binary>>
end.
-file("src/bench/bench.gleam", 264).
?DOC(" Print summary of a benchmark suite\n").
-spec print_suite_summary(bench_suite()) -> nil.
print_suite_summary(Suite) ->
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
begin
_pipe = <<"="/utf8>>,
gleam@string:repeat(_pipe, 60)
end
),
gleam_stdlib:println(
<<"Summary: "/utf8, (erlang:element(2, Suite))/binary>>
),
gleam_stdlib:println(
begin
_pipe@1 = <<"="/utf8>>,
gleam@string:repeat(_pipe@1, 60)
end
),
gleam@list:each(
erlang:element(3, Suite),
fun(Result) ->
gleam_stdlib:println(
<<<<<<<<" "/utf8,
(pad_right(erlang:element(2, Result), 30))/binary>>/binary,
": "/utf8>>/binary,
(format_duration_us(erlang:element(5, Result)))/binary>>/binary,
"/op"/utf8>>
)
end
),
gleam_stdlib:println(<<""/utf8>>).
-file("src/bench/bench.gleam", 400).
?DOC(" Pad a string on the left to a given length\n").
-spec pad_left(binary(), integer()) -> binary().
pad_left(S, Len) ->
Current_len = string:length(S),
case Current_len >= Len of
true ->
S;
false ->
<<(gleam@string:repeat(<<" "/utf8>>, Len - Current_len))/binary,
S/binary>>
end.
-file("src/bench/bench.gleam", 237).
?DOC(" Print a single benchmark result\n").
-spec print_result(bench_result()) -> nil.
print_result(Result) ->
Avg_str = format_duration_us(erlang:element(5, Result)),
Total_str = <<(erlang:integer_to_binary(erlang:element(3, Result)))/binary,
"ms"/utf8>>,
Iter_str = <<(erlang:integer_to_binary(erlang:element(4, Result)))/binary,
" iters"/utf8>>,
Extra = case {erlang:element(6, Result), erlang:element(7, Result)} of
{{some, Min}, {some, Max}} ->
<<<<<<<<" (min: "/utf8, (format_duration_us(Min))/binary>>/binary,
", max: "/utf8>>/binary,
(format_duration_us(Max))/binary>>/binary,
")"/utf8>>;
{_, _} ->
<<""/utf8>>
end,
gleam_stdlib:println(
<<<<<<<<<<<<(pad_right(erlang:element(2, Result), 35))/binary,
(pad_left(Avg_str, 12))/binary>>/binary,
"/op "/utf8>>/binary,
(pad_left(Total_str, 8))/binary>>/binary,
" total "/utf8>>/binary,
(pad_left(Iter_str, 12))/binary>>/binary,
Extra/binary>>
).
-file("src/bench/bench.gleam", 200).
?DOC(" Run all benchmarks in a suite\n").
-spec run_suite(binary(), list({binary(), fun(() -> any())}), bench_config()) -> bench_suite().
run_suite(Suite_name, Benchmarks, Config) ->
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
begin
_pipe = <<"="/utf8>>,
gleam@string:repeat(_pipe, 60)
end
),
gleam_stdlib:println(<<"Benchmark Suite: "/utf8, Suite_name/binary>>),
gleam_stdlib:println(
begin
_pipe@1 = <<"="/utf8>>,
gleam@string:repeat(_pipe@1, 60)
end
),
gleam_stdlib:println(
<<<<<<<<"Config: "/utf8,
(erlang:integer_to_binary(erlang:element(3, Config)))/binary>>/binary,
" iterations, "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(2, Config)))/binary>>/binary,
" warmup"/utf8>>
),
gleam_stdlib:println(
begin
_pipe@2 = <<"-"/utf8>>,
gleam@string:repeat(_pipe@2, 60)
end
),
Results = gleam@list:map(
Benchmarks,
fun(Bench) ->
{Name, F} = Bench,
Result = benchmark_with_config(Name, Config, F),
print_result(Result),
Result
end
),
gleam_stdlib:println(
begin
_pipe@3 = <<"-"/utf8>>,
gleam@string:repeat(_pipe@3, 60)
end
),
gleam_stdlib:println(<<""/utf8>>),
{bench_suite, Suite_name, Results}.
-file("src/bench/bench.gleam", 413).
?DOC(" Compare two benchmark results and return percentage change\n").
-spec compare_results(bench_result(), bench_result()) -> float().
compare_results(Baseline, Current) ->
(case erlang:element(5, Baseline) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> (erlang:element(5, Current) - erlang:element(
5,
Baseline
))
/ Gleam@denominator
end) * 100.0.
-file("src/bench/bench.gleam", 418).
?DOC(" Check if a benchmark result is within acceptable threshold of baseline\n").
-spec within_threshold(bench_result(), bench_result(), float()) -> boolean().
within_threshold(Baseline, Current, Threshold_percent) ->
Change = compare_results(Baseline, Current),
Change < Threshold_percent.