%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 简单的性能测试工具，匿名函数的影响有限，
%%% 但是方便不修改工具直接外部添加测试用例进行对比使用，
%%% 所以忽略匿名函数对测试结果的影响，做基本的性能判断
%%%
%%% 对比测试方式：
%%%     ptester:run(Times, [
%%%             {Fun1Label, Fun1/1},
%%%             {Fun2Label, Fun2/1},
%%%             ...
%%%         ]).
%%% 单个函数测试:
%%%     ptester:timer(FunLabel, Fun/1, Times).
%%% @end
%%% -------------------------------------------------------
-module(ptester).
-compile(export_all).

%% 运行一个测试集
run(N, List) ->
    [[L, T1, T2] = H | T] = [timer(Label, F, N) || {Label, F} <- List],
    io:format("========================================================================~n"),
    io:format("~20s = ~9.2fms [~8.2f%] ~9.2fms [~8.2f%]~n", [L, T1 + 0.0, 100.0, T2 + 0.0, 100.0]),
    compare(T, H),
    io:format("========================================================================~n").

%% 运行单项测试并计时
timer(Label, F, N) ->
    % WordSize = erlang:system_info(wordsize),
    % 分配较大的初始空间，减少gc对测试结果的影响，以期获得更准确的结果
    % MinHeapSize = 20 * 1024 * 1024,
    % MinBinVHeapSize = MinHeapSize,
    % erlang:process_flag(min_heap_size, MinHeapSize),
    % erlang:process_flag(min_bin_vheap_size, MinBinVHeapSize),
    % 先进行gc一次，避免多个函数测试时，上一次的测试对进程gc的相关影响
    erlang:garbage_collect(self()),
    statistics(runtime),
    statistics(wall_clock),
    for(1, N, F),
    {_, Time1} = statistics(runtime),
    {_, Time2} = statistics(wall_clock),
    U1 = Time1 * 1000 / N,
    U2 = Time2 * 1000 / N,
    io:format("~p [total: ~p(~p)ms avg: ~.3f(~.3f)us]~n", [Label, Time1, Time2, U1, U2]),
    [Label, Time1, Time2].

%% 比较结果
compare([], _) ->
    ok;
compare([[L, T1, T2] | T], [_, TB1, TB2] = TB) ->
    io:format(
        "~20s = ~9.2fms [~8.2f%] ~9.2fms [~8.2f%]~n",
        [L, T1 + 0.0, T1 / (TB1 + 0.00000001) * 100, T2 + 0.0, T2 / (TB2 + 0.00000001) * 100]
    ),
    compare(T, TB).

%% for循环
for(Max, Max, F) -> F(Max);
for(I, Max, F)   -> F(I), for(I + 1, Max, F).

%% 产生随机数
rand(Same, Same) -> Same;
rand(Min, Max) when Max < Min ->
    rand(Max, Min);
rand(Min, Max) ->
    case get("rand_seed") of
        undefined ->
            RandSeed = os:timestamp(),
            rand:seed(RandSeed),
            put("rand_seed", RandSeed);
        _ ->
            skip
    end,
    M = Min - 1,
	rand:uniform(Max - M) + M.

