Previous Up Next

6.9  Writing Efficient Code

The ECLiPSe compiler tries its best, however there are some constructs which can be compiled more efficiently than others. On the other hand, many Prolog programmers overemphasize the importance of efficient code and write completely unreadable programs which can be only hardly maintained and which are only marginally faster than simple, straightforward and readable programs. The advice is therefore Try the simple and straightforward solution first! The second rule is to keep this original program even if you try to optimise it. You may find out that the optimisation was not worth the effort.

To achieve the maximum speed of your programs, you must produce the optimised code with the flag debug_compile being off, e.g. by calling set_flag(debug_compile, off), or using the pragma nodebug. Setting the flag variable_names can also cause slight performance degradations and it is thus better to have it off, unless variable names have to be kept. Unlike in the previous releases, the flag coroutine has now no influence on the execution speed. Some programs spend a lot of time in the garbage collection, collecting the stacks and/or the dictionary. If the space is known to be deallocated anyway, e.g. on failure, the programs can be often speeded up considerably by switching the garbage collector off or by increasing the gc_interval flag. As the global stack expands automatically, this does not cause any stack overflow, but it may of course exhaust the machine memory.

When the program is running and its speed is still not satisfactory, use the profiling tools. The profiler can tell you which predicates are the most expensive ones, and the statistics tool tells you why. A program may spend its time in a predicate because the predicate itself is very time consuming, or because it was frequently executed. The statistics tool gives you this information. It can also tell whether the predicate was slow because it has created a choice point or because there was too much backtracking due to bad indexing.

One of the very important points is the selection of the clause that matches the current call. If there is only one clause that can potentially match, the compiler is expected to recognise this and generate code that will directly execute the right clause instead of trying several subsequent clauses until the matching one is found. Unlike most of the current Prolog compilers, the ECLiPSe compiler tries to base this selection (indexing) on the most suitable argument of the predicate4. It is therefore not necessary to reorder the predicate arguments so that the first one is the crucial argument for indexing. However, the decision is still based only on one argument. If it is necessary to look at two arguments in order to select the matching clause, e.g. in
p(a, a) :- a.
p(b, a) :- b.
p(a, b) :- c.
p(d, b) :- d.
p(b, c) :- e.
and if it is crucial that this procedure is executed as fast as possible, it is necessary to define an auxiliary procedure which can be indexed on the other argument:
p(X, a) :- pa(X).
p(X, b) :- pb(X).
p(b, c) :- e.

pa(a) :- a. pa(b) :- b.

pb(a) :- c. pb(d) :- d.
The compiler also tries to use for indexing all type-testing information that appears at the beginning of the clause body: If the compiler can decide about the clause selection at compile time, the type tests are never executed and thus they incur no overhead. When the clauses are not disjoint because of the type tests, either a cut after the test or more tests into the other clauses can be added. For example, the following procedure will be recognised as deterministic and all tests are optimised away:
    % a procedure without cuts
    p(X) :- var(X), ...
    p(X) :- (atom(X); integer(X)), X \= [], ...
    p(X) :- nonvar(X), X = [_|_], ...
    p(X) :- nonvar(X), X = [], ...
Another example:
    % A procedure with cuts
    p(X{_}) ?- !, ...
    p(X) :- var(X), !, ...
    p(X) :- integer(X), ...
    p(X) :- real(X), ...
    p([H|T]) :- ...
    p([]) :- ...
Integers less than or greater than a constant can also be recognised by the compiler:
    p(X) :- integer(X), X < 5, ...
    p(7) :- ...
    p(9) :- ...
    p(X) :- integer(X), X >= 15, ...
If the clause contains tests of several head arguments, only the first one is taken into account for indexing.

Here are some more hints for efficient coding with ECLiPSe:
Previous Up Next