2.18 Just-in-time clause indexing

SWI-Prolog provides‘just-in-time' indexing over multiple arguments.35JIT indexing was added in version 5.11.29 (Oct. 2011). ‘Just-in-time' means that clause indexes are not built by the compiler (or asserta/1 for dynamic predicates), but on the first call to such a predicate where an index might help (i.e., a call where at least one argument is instantiated). This section describes the rules used by the indexing logic. Note that this logic is not‘set in stone'. The indexing capabilities of the system will change. Although this inevitably leads to some regressing on some particular use cases, we strive to avoid significant slowdowns.

The list below describes the clause selection process for various predicates and calls. The alternatives are considered in the order they are presented.

The library library(prolog_jiti) provides jiti_list/0,1 to list the characteristics of all or some of the created hash tables.

Dynamic predicates are indexed using the same rules as static predicates, except that the special purpose schemes are never applied. In addition, the JITI index is discarded if the number of clauses has doubled since the predicate was last assessed or shrinks below one fourth. A subsequent call reassesses the statistics of the dynamic predicate and, when applicable, creates a new index.

2.18.1 Deep indexing

As introduced in section 2.18, deep indexing creates hash tables distinguish clauses that share a compound with the same name and arity. Deep indexes allow for efficient lookup of arbitrary terms. Without it is advised to flatten the term, i.e., turn F(X) into two arguments for the fact, one argument denoting the functor F and the second the argument X. This works fine as long as the arity of the each of the terms is the same. Alternatively we can use term_hash/2 or term_hash/4 to add a column holding the hash of the term. That approach can deal with arbitrary arities, but requires us to know that the term is ground (term_hash/2) or up to which depth we get sufficient selectivity (term_hash/4).

Deep indexing does not require this knowledge and leads to efficient lookup regardless of the instantiation of the query and term. The current version does come with some limitations:

Note that, when compiling DCGs (see section 4.13) and the first body term is a literal, it is included into the clause head. See for example the grammar and its plain Prolog representation below.

det(det(a), sg)  --> "a".
det(det(an), pl) --> "an".
det(det(the), _) --> "the".
?- listing(det).
det(det(a), sg, [97|A], A).
det(det(an), pl, [97, 110|A], A).
det(det(the), _, [116, 104, 101|A], A).

Deep argument indexing will create indexes for the 3rd list argument, providing speedup and making clause selection deterministic if all rules start with a literal and all literals are unique in the first 6 elements. Note that deep index creation stops as soon as a deterministic choice can be made or there are no two clauses that have the same name/arity combination.

2.18.2 Future directions

2.18.3 Indexing for body code

The current SWI-Prolog versions only consider the head for generating clause indexing. This would make it impossible to examine a head argument and pass the argument in the body without copying the argument. Consider the two clauses below. Both have equal semantics under Prolog. The first version would loose clause indexing while the second creates a copy of the f/1 argument. Neither is desirable.

p(X) :- X = f(I), integer(I), q(X).
p(f(I)) :- integer(I), q(f(X)).

As of SWI-Prolog 8.3.21, unifications against head arguments that happen before anything else in the body are compiled special. Effectively, the term unified too is moved into the head (providing indexing) and places where this term is used simply use the corresponding argument. The explicit unification is removed. Decompilation (clause/2) reverses this process, but may not produce exactly the same term. The re-inserted unfications are ordered according to the argument position and the variable is always on the left hand of the =/2. Thus,

p(X,Y) :- f(_) = Y, X = g(_), q(X,Y).

Is decompiled into the following equivalent clause.

p(X,Y) :- X = g(_), Y = f(_), q(X,Y).

Additional notes:

2.18.4 Indexing and portability

The base-line functionality of Prolog implementations provides indexing on constants and functor (name/arity) on the first argument. This must be your assumption if wide portability of your program is important. This can typically be achieved by exploiting term_hash/2 or term_hash/4 and/or maintaining multiple copies of a predicate with reordered arguments and wrappers that update all implementations (assert/retract) and selects the appropriate implementation (query).

YAP provides full JIT indexing, including indexing arguments of compound terms. YAP's indexing has been the inspiration for enhancing SWI-Prolog's indexing capabilities.