Making a Prolog program more efficient by transforming its source code, without changing its operational semantics, is not an obvious task. It requires the user to have a clear understanding of how the Prolog compiler works, and in particular, of the effects of impure features like the cut. The way a Prolog code is written - e.g., the order of clauses, the order of literals in a clause, the use of cuts or negations - influences its efficiency. Furthermore, different optimization techniques may be redundant or conflicting when they are applied together, depending on the way a procedure is called - e.g., inserting cuts and enabling indexing. We present an optimiser, based on abstract interpretation, that automatically performs safe code transformations of Prolog procedures in the context of some class of input calls. The method is more effective if procedures are annotated with additional information about modes, types, sharing, number of solutions and the like. Thus the approach is similar to Mercury. It applies to any Prolog program, however.
Programming in Prolog allows us to write so-called multidirectional procedures, in the sense that the same code of a procedure can be used in more than one way (the arguments being either input data or output results). The undesirable consequence for multidirectionality is inefficiency (in terms of space utilisation and of execution time). This efficiency issue comes from the general execution model of Prolog, which is generally based on the Warren's Abstract Machine (WAM, for short) [1,14]. Answer substitutions are computed according to a depth-first search strategy with backtracking, where clauses are executed from top-to-bottom, and literals are executed from left-to-right inside a clause. Given the incompleteness of Prolog, some input-output patterns can loop. Also, Prolog uses a general algorithm for unification, with no restriction on the terms being unified, but most compilers do not perform the occur-check test during the unification. For efficiency reasons, some built-in procedures are not multidirectional (for example, arithmetic and comparison predicates). Negation as failure is sound only if it applies to a ground literal. Due to the incompleteness and unsoundness of Prolog, not every ordering of clauses and literals is operationally correct, and the way a procedure is written greatly influences the search of its solutions, and then, the efficiency.
A solution for optimising multidirectional procedures is to generate specialised code for each particular use of the procedure. In the context of a directionality, one can try to find a more efficient ordering of clauses and literals, such that the program still remains operationally correct for that directionality. In Prolog, we can also try to insert cuts to prune the search tree without removing solutions. This can greatly reduce the size of the search tree, and improves the efficiency. Applying correct code transformations is not obvious and is tricky to be done manually, because it is very error-prone. This paper describes an optimiser based on abstract interpretation, which realizes this task automatically.
To illustrate the interest of specialising code, consider the mutidirectional procedure efface(X,T,TEff), which is the running example of [6]: X is an element of list T, and TEff is the list T without the first occurrence of X in T.
This code can be used in several ways: either when every input argument is a ground term; or when inputs X and T are ground and TEff is a variable; or when inputs X and TEff are ground and X is a variable; or when inputs X and TEff are variables and T is ground; etc. Now, if we consider only the first directionality, then our optimiser will be able to automatically generate the following specialised code (the optimiser has checked that the procedure is deterministic for this directionality): efface(X,[X|T],T) :-!. efface(X,[H|T], [H|TEff]) :-efface(X,T,TEff).
The clauses are reordered, a cut has been inserted in the first clause, and the negation is removed. For all inputs satisfying the first directionality, the sequences of answer substitutions of the specialised and of the multidirectional codes are identical. Table 1 compares the execution between the multidirectional and the specialised codes. Several tests have been performed by varying the list-length of the input list T. The table shows that the multidirectional code is less efficient than the specialised one in terms of execution time and of used local stack. In particular, the specialised code uses a constant amount of local stack (independently of the size of the input), while we yield a local stack error if we try to execute the multidirectional code with an input list of size 25000. The speedup increases according to the size of the input: for instance, the optimised code spent 3.61 times less execution time for an input list of size 10000.
Our approach is strongly inspired by [6], where a methodology to build correct programs is proposed: starting from a specification and a so-called logic description of the problem, the methodology constructs operationally correct programs which are not written in the usual style of experienced Prolog programmers: procedures are normalised, with explicit unifications, and are thus inefficient. The author of [6] then proposes to apply some code transformations, in order to produce more efficient programs (written in the usual style and where cuts are introduced). It is not obvious to ensure the correct application of transformations, nor to choose the best order to apply them. Our optimiser does not require or assume that Prolog programs are written in a specialised syntax. It accepts any kind of Prolog programs. So, the programmer has the liberty to write its program in the style he wants: normalised or not. Our optimiser then automatically specialises the program for some directionality, by choosing a suitable order for applying the transformations, and by ensuring that the transformations are correctly appli
This content is AI-processed based on open access ArXiv data.