The issues which affect performance of a rule- based system are considerably different from those which affect conventional programs. This section discusses the single most important issue: the ordering of patterns on the LHS of a rule.
CLIPS is a rule language based on the RETE algorithm. The RETE algorithm was designed specifically to provide very efficient pattern- matching. CLIPS has attempted to implement this algorithm in a manner that combines efficient performance with powerful features. When used properly, CLIPS can provide very reasonable performance, even on microcomputers. However, to use CLIPS properly requires some understanding of how the pattern- matcher works.
Prior to initiating execution, each rule is loaded into the system and a network of all patterns that appear on the LHS of any rule is constructed. As facts and instances of reactive classes (referred to collectively as pattern entities) are created, they are filtered through the pattern network. If the pattern entities match any of the patterns in the network, the rules associated with those patterns are partially instantiated. When pattern entities exist that match all patterns on the LHS of the rule, variable bindings (if any) are considered. They are considered from the top to the bottom; i.e., the first pattern on the LHS of a rule is considered, then the second, and so on. If the variable bindings for all patterns are consistent with the constraints applied to the variables, the rules are activated and placed on the agenda.
This is a very simple description of what occurs in CLIPS, but it gives the basic idea. A number of important considerations come out of this. Basic pattern- matching is done by filtering through the pattern network. The time involved in doing this is fairly constant. The slow portion of basic pattern- matching comes from comparing variable bindings across patterns. Therefore, the single most important performance factor is the ordering of patterns on the LHS of the rule. Unfortunately, there are no hard and fast methods that will always order the patterns properly. At best, there seem to be three "quasi" methods for ordering the patterns.
These rules are not independent and commonly conflict with each other. At best, they provide some rough guidelines. Since all systems have these characteristics in different proportions, at a glance the most efficient manner of ordering patterns for a given system is not evident. The best approach is to develop the rules with minimal consideration of ordering. When the reasoning is fairly well verified, experiment with the patterns until the optimum configuration is found.
Another performance issue is the use of multifield variables and wildcards ($?). Although they provide a powerful capability, they must be used very carefully. Since they can bind to zero or more fields, they can cause multiple instantiations of a single rule. In particular, the use of multiple multifield variables in one pattern can cause a very large number of instantiations.
Some final notes on rule performance. Experience suggests that the user should keep the expert system "lean and mean." The list of pattern entities should not be used as a data base for storage of extraneous information. Store and pattern- match only on that information necessary for reasoning. Keep the pattern- matching to a minimum and be as specific as possible. Many short, simple rules perform better than long, complex rules and have the added benefit of being easier to understand and maintain.
Deffunctions execute more quickly than generic function because generic functions must first examine their arguments to determine which methods are applicable. If a generic function has only one method, a deffunction probably would be better. Care should be taken when determining if a particular function truly needs to be overloaded. In addition, if recompiling and relinking CLIPS is not prohibitive, user- defined external functions are even more efficient than deffunctions. This is because deffunction are interpreted whereas external functions are directly executed. For more details, see sections 7 and 8.2.
When the generic dispatch examines a generic function's method to determine if it is applicable to a particular set of arguments, it examines that method's parameter restrictions from left to right. The programmer can take advantage of this by placing parameter restrictions which are less frequently satisfied than others first in the list. Thus, the generic dispatch can conclude as quickly as possible when a method is not applicable to a generic function call. If a group of restrictions are all equally likely to be satisfied, placing the simpler restrictions first, such as those without queries, will also allow the generic dispatch to conclude more quickly for a method that is not applicable. For more details, see section 8.4.3.
COOL allows instances of user- defined classes to be referenced either by address or by name in functions which manipulate instances, such as message- passing with the send function. However, when an instance is referenced by name, CLIPS must perform an internal lookup to find the instanceaddress anyway. If the same instance is going to be manipulated many times, it might be advantageous to store the instance- address and use that as a reference. This will allow CLIPS to always go directly to the instance. For more details, see sections 2.4.2 and 12.16.4.6.
Normally, message- passing must be used to read or set a slot of an instance. However, slots can be read directly within instanceset queries and messagehandlers, and they can be set directly within message- handlers. Accessing slots directly is significantly faster than message- passing. Unless message- passing is required (because of slot daemons), direct access should be used when allowed. For more details, see sections 9.4.2, 9.4.3, 9.4.4, 9.6.3, 9.6.4 and 9.7.3.