Profiler

## 6.2 Profiler

The profiling tool helps to find hot spots in a program that are worth optimising. It can be used any time with any compiled Prolog code, it is not necessary to use a special compilation mode or set any flags. Note however that it is not available on Windows. When

>     ?- profile(Goal).

is called, the profiler executes the Goal in the profiling mode, which means that every 100th of a second the execution is interrupted and the profiler records the currently executing procedure.

Consider the following n-queens code.

>     queen(Data, Out) :-
>             qperm(Data, Out),
>             safe(Out).

>     qperm([], []).
>     qperm([X|Y], [U|V]) :-
>             qdelete(U, X, Y, Z),
>             qperm(Z, V).

>     qdelete(A, A, L, L).
>     qdelete(X, A, [H|T], [A|R]) :-
>             qdelete(X, H, T, R).

>     safe([]).
>     safe([N|L]) :-
>             nodiag(L, N, 1),
>             safe(L).

>     nodiag([], _, _).
>     nodiag([N|L], B, D) :-
>             D =\= N - B,
>             D =\= B - N,
>             D1 is D + 1,
>             nodiag(L, B, D1).

Issuing the following query will result in the profiler recording the currently executing goal 100 times a second.

>     ?- profile(queen([1,2,3,4,5,6,7,8,9],Out)).
>     goal succeeded

>                     PROFILING STATISTICS
>                     --------------------

>     Goal:             queen([1, 2, 3, 4, 5, 6, 7, 8, 9], Out)
>     Total user time:  0.03s

>     Predicate             Module        %Time   Time   %Cum
>     --------------------------------------------------------
>     qdelete           /4  eclipse       50.0%   0.01s  50.0%
>     nodiag            /3  eclipse       50.0%   0.01s 100.0%

>     Out = [1, 3, 6, 8, 2, 4, 9, 7, 5]
>     Yes (0.14s cpu)

From the above result we can see how the profiler output contains four important areas of information:

1. The first line of output indicates whether the specified goal succeeded, failed or aborted. The `profile/1` predicate itself always succeeds.
2. The line beginning `Goal:` shows the goal which was profiled.
3. The next line shows the time spent executing the goal.
4. Finally the predicates which were being executed when the profiler sampled, ranked in decreasing sample count order are shown.

The problem with the results displayed above is that the sampling frequency is too low when compared to the total user time spent executing the goal. In fact in the above example the profiler was only able to take two samples before the goal terminated.

The frequency at which the profiler samples is fixed, so in order to obtain more representative results one should have an auxiliary predicate which calls the goal a number of times, and compile and profile a call to this auxiliary predicate. eg.

>     queen_100 :-
>       (for(_,1,100,1) do queen([1,2,3,4,5,6,7,8,9],_Out)).

Note that, when compiled, the above `do/2` loop would be efficiently implemented and not cause overhead that would distort the measurement.

⊙
  See section 4.2 for more information on logical loops

>     ?- profile(queen_100).
>     goal succeeded

>                     PROFILING STATISTICS
>                     --------------------

>     Goal:             queen_100
>     Total user time:  3.19s

>     Predicate             Module        %Time   Time   %Cum
>     --------------------------------------------------------
>     nodiag            /3  eclipse       52.2%   1.67s  52.2%
>     qdelete           /4  eclipse       27.4%   0.87s  79.6%
>     qperm             /2  eclipse       17.0%   0.54s  96.5%
>     safe              /1  eclipse        2.8%   0.09s  99.4%
>     queen             /2  eclipse        0.6%   0.02s 100.0%

>     Yes (3.33s cpu)

In the above example, the profiler takes over three hundred samples resulting in a more accurate view of where the time is being spent in the program. In this instance we can see that more than half of the time is spent in the `nodiag/3` predicate, making it an ideal candidate for optimisation. This is left as an exercise for the reader.