:- module(_,_).

% - Slow, difficult to understand, long version (exaggerated):

swap_slow(S1, S2) :-
    functor(S1, f, 2),
    functor(S2, f, 2),
    arg(1, S1, X1),
    arg(2, S1, Y1),
    arg(1, S2, X2),
    arg(2, S2, Y2),
    X1 = Y2,
    X2 = Y1.

% - Fast, intuitive, shorter version:

swap(f(X, Y), f(Y, X)).

%   Try:
% ?- swap_slow(f(a,b),S).
% ?- swap(f(a,b),S).

% - The speedup, swapping a million times:

swaps_slow(0).
swaps_slow(N) :-
    N > 0,
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    swap_slow(f(a,b), _),
    N1 is N - 1,
    swaps_slow(N1).

swaps(0).
swaps(N) :-
    N > 0,
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    swap(f(a,b), _),
    N1 is N - 1,
    swaps(N1).

swaps_loop(0).
swaps_loop(N) :-
    N > 0,
    N1 is N - 1,
    swaps_loop(N1).

% ?- speedup_loop(swaps_loop(100000), swaps_slow(100000), swaps(100000)).

% - Auxiliary predicates, used in all the examples of this part:
%   `speedup(Slow, Fast)` shows the time (in ms) of one run of each
%   goal, and how many times faster `Fast` is. Each goal is repeated
%   until it has run for at least 100 ms, and the time of the loop
%   itself is subtracted, so that short goals can also be measured.
%   `speedup_loop(Loop, Slow, Fast)` also subtracts the time of `Loop`
%   (the empty loop around the calls being compared).

speedup(Slow, Fast) :-
    speedup_loop(true, Slow, Fast).

speedup_loop(Loop, Slow, Fast) :-
    time_of(Loop, Slow, TS),
    time_of(Loop, Fast, TF),
    show_speedup(TS, TF).

time_of(Loop, Goal, T) :-
    repeat_time(1, Loop, Goal, T).

repeat_time(N, Loop, Goal, T) :-
    loop_time(N, Goal, TG),
    (   TG >= 100
    ->  loop_time(N, Loop, TL),
        (   TG - TL > TG / 10        % (else too fast to tell
        ->  T is (TG - TL) / N       %  from the loop itself)
        ;   T = 0
        )
    ;   N1 is N * 10,
        repeat_time(N1, Loop, Goal, T)
    ).

loop_time(N, Goal, T) :-
    statistics(runtime, [T0, _]),
    ( between(1, N, _), once(Goal), fail ; true ),
    statistics(runtime, [T1, _]),
    T is T1 - T0.

show_speedup(TS, TF) :-
    format("Slow: ", []), show_time(TS),
    format(", fast: ", []), show_time(TF),
    (   TS > 0, TF > 0
    ->  S is TS / TF,
        format(", speedup: ~1f~n", [S])
    ;   nl
    ).

show_time(T) :-
    T > 0, !,
    format("~4f ms", [T]).
show_time(_) :-
    format("too fast to measure", []).

:- use_module(library(between)).
