Port Statistics

In the following we will customize trace_goal/2. The idea is to provide a custom debugger call back that counts the invoked goal ports. We can do so by providing additional rules for the multi-file predicate goal_tracing/2. This predicate takes as an argument the port identifier and the goal frame. We would like to record the port invocation on a per goal predicate basis. We will use the following Prolog fact to record the invocation:

% count(Fun, Arity, CallExitRedoFail).

The third argument will be an aggregate of the number of invocations for the corresponding ports. We can retrieve the predicate indicator from a goal frame via the system predicate frame_property/2. The custom call-back then reads as follows:

% goal_tracing(+Port, +Frame)
goal_tracing(P, Q) :-
frame_property(Q, sys_call_goal(G)),
functor(G, F, A),
get_delta(P, D),
update_count(F, A, D).

The custom call back makes use of the predicates get_delta/2 and update_count/3. The predicate get_delta/2 will determine which part of the aggregate has to be incremented depending on the port. And the predicate update_count/3 will do the increment. Besides the custom call back we have also provided the commands show/0 and reset/0. More details on the implementation can be found in the appendix.

Let’s do some sample runs. We will look into the port statistics of the Peano factorial program. We first need to consult both the call back and the target program:

?- ['fac.p', 'count.p'].

We can now measure one particular predicate in that we set a spy point on it. This is done with the usual debugger command spy/2. We will measure the add/3 predicate:

?- spy(add/3).

All that remains to do is switching the debugger on and running our example program. To switch the debugger on we also use the usual debugger command debug/2. We will run the Peano factorial program for the Peano number 7:

?- debug.
?- fac(s(s(s(s(s(s(s(n))))))),_).

The program will behave nearly as executed without debugging. The only thing that can be seen is performance degradation and maybe a change in determinism. The performance degradation is the net effect of checking the debugger conditions and invoking the custom call back. The change in determinism is currently the effect of the execution event handlers which introduce additional choice points.

When the execution has finished we can proceed in looking at the results. We will first switch the debugger off so that our results are not spoiled by reporting function. We get the following results:

?- nodebug.
?- show.
Pred    Call    Exit    Redo    Fail
add / 3 5941    5941    0    0

Instead of setting a spy point we can also run the example program in trace mode. Then the call back will be invoked for every port of a traceable goal. But before we run our example program again we will reset the port statistics:

?- reset.
?- trace.
?- fac(s(s(s(s(s(s(s(n))))))),_).

The results will now not only include the add/3 predicate, but also the mul/3 and fac/2 predicate from the Peano factorial program. The result for the add/3 predicate should not differ from our previous result since ports are independently counted. We get the following results:

?- nodebug.
?- show.
Pred    Call    Exit    Redo    Fail
add / 3 5941    5941    0    0
mul / 3 35    35    0    0
fac / 2 8    8    0    0

The above port statistics for the Peano program does not show any redo or fail counts. The main reason is that we did not cause a redo of our main query for a factorial of the Peano 7. As a result after the success of the main query all the remaining choice points were removed. The process of removing choice points is not visible to the instrumentation.

We night ask whether a more refined port statistic is possible. Currently we support call site identification down to the line number of the active clause. It is planned to also allow the identification of the line number of the active goal, but this has not yet been implemented. The call site can be accessed via a further frame property. Each goal frame points to a text frame, which can in turn be used to access the associated clause.

We can retrieve the clause source file and line number from a clause reference via the system predicate clause_property/2. The modified call-back then reads as follows:

% goal_tracing(+Port, +Frame)
goal_tracing(P, Q) :-
frame_property(Q, sys_call_goal(G)),
functor(G, F, A),
atom_property(F, source_file(O)),
atom_property(F, line_no(L)), !,
get_delta(P, D),
update_count_predicate(F, A, D),
update_count_source(F, A, O, L, D).

In the above code we update two counters, namely the predicate level and the call site level counters. The predicate level counter will thus show the sum of the call site level counters. We could have opted for another design, where the sums are determined during the reporting. The call-back would thus have less work to do and therefore run faster. But for brevity we kept the solution simple. More details on the implementation can be found in the appendix.

If we run our Peano factorial program again we will get a more detailed port statistics. We get the following results:

?- show.
Pred    Source    Line    Call    Exit    Redo    Fail
add / 3            5941    5941    0    0
    fac.p    9    5913    5913    0    0
    fac.p    12    28    28    0    0
mul / 3            35    35    0    0
    fac.p    12    28    28    0    0
    fac.p    15    7    7    0    0
fac / 2            8    8    0    0
    fac.p    15    7    7    0    0
        3    1    1    0    0

The port statistics has multiple application areas. It can be used to empirically investigate into the complexity of a Prolog program. Or it can be used to determine the coverage of test cases. For the later purpose one simple runs the test cases with port statistics and can then check what predicates have been touched.

Kommentare