| Prev | Next | Start of Chapter | Next Chapter | Contents | Glossary | Index | Comments | (4 out of 4)

Guidelines for Maximizing Performance

This section gives a variety of guidelines for optimizing your application. The guidelines are organized by category.


Caution: The implementation of G2 is subject to change without notice, which can affect the relative efficiency of different types of actions. The following guidelines are based on experience with G2 5.0, Rev. 1. Improvements in future G2 versions might lead to modifications in some of these guidelines.

Declarations

Make Type Declarations as Specific as Possible

To reduce the amount of real-time type checking, tightly type all attributes, local variables, and procedure arguments. You should try to avoid statements that assign a more general class or value type to a local variable that has a more specific type declaration, because this forces G2 to check if the assignment is valid. For example, the statement X = Y is most efficient when X and Y have identical type declarations, and it is least efficient when Y is a superior class of, or a more general value type, than X.


Note: The one exception to tight typing is casting local variables to native data types to improve performance.

For more information, see the references in this table:

For more information on... See...
Declaring typed attributes
Guidelines for Declaring Typed Attributes
Casting
Use Casting When Binding Array Subclasses to Local Variables
Supporting data
Effect of Tight Typing versus Loose Typing

Use Local Variable Assignment Statements Wisely

In general, you should assign a local variable when you need to reference a value more than once because references to local variables are fast. Conversely, do not make a local variable assignment if you are referencing a value only once.

For data that support this recommendation, see Comparison of Various Types of References.

Use Stability Declarations

You can obtain significant increases in performance by using stability declarations, which allow you to designate key classes and procedures stable for dependent compilations. Stability declarations improve performance by:

One approach to using stability declarations is to declare every item in the KB to be stable, by default, and only declare items to be unstable when you know they are going to change at run-time. To declare every item stable, declare the top-level workspace of each module in the application to be stable.

You should only declare all items in a KB stable if your application provides a GUI front-end to all the items in your KB. If your application provides public items on public workspaces as part of the user-visible GUI, you should only declare private items to be stable. The reason for this has to do with upgrading text-stripped KBs, as the following note cautions.


Caution: If you distribute a text-stripped KB that uses stability declarations and you distribute an upgraded KB, the user of the KB will not be able to run the KB. For this reason, we recommend that you declare only private items to be stable when you distribute a text-stripped KB.

While you can also declare the entire KB to be stable by editing the KB Configuration system table, we do not recommend this approach for large applications that include standard utility modules such as UIL, because the stability declarations apply to all modules in the KB.

To declare an item stable for dependent compilations:

If you have declared the entire hierarchy stable, you can declare individual items unstable by specifying item configurations for specific items.

To declare an item unstable for dependent compilations:

Once you have declared your KB stable, you must recompile every item by using the Inspect facility. If you edit any item that you have declared to be stable, either interactively or programmatically, G2 warns you that you are editing a stable item.


Note: If you have an item that depends in some way on a stable item and you edit that item, G2 recompiles not only the stable item but also the dependent item.

In addition, we recommend that you not declare public API procedures to be stable. Otherwise, if you change the argument types of the procedure and redistribute the module, you will force a recompile of all items that depend on the procedure.

For more information on using stability declarations, see the G2 Reference Manual.

For data that support this recommendation, see the references in this table:

For information on... See...
Using stability declarations with lists and arrays
Effect of Subclassing, Stability, and Casting on Array References
Using stability declarations with typing
Effect of Stability Declarations on Tight versus Loose Typing
Using stability declarations with method and procedure calls
Comparing Method, Procedure, and Function Calls

Data Representation

Use Arrays for Fast Direct Access and Data Storage

Arrays are the best data structure for quick data access and storage under these conditions:

In particular:

Always subclass from tightly typed arrays such as integer-array, symbol-array, and so on, instead of weakly typed arrays such as g2-array or value-array, if the type of value to be stored is known in advance.

To maintain maximum efficiency, always declare the array subclass to be stable for dependent compilation.

For more information, see the references in this table:

For information on... See...
Using stability declarations
Use Stability Declarations
Using stability declarations with arrays
Effect of Subclassing, Stability, and Casting on Array References
Casting
Use Casting When Binding Array Subclasses to Local Variables
Data that support these recommendations
Comparison of Various Types of References

Use List for Fast Manipulation of Elements

Lists are the most efficient data structure when the number of elements in a series is not known or might vary significantly at run-time, or when you need to insert or delete elements at run-time, when direct access to the list elements is not required.

For more information, see the references in this table:

For information on... See...
Manipulating lists
Use Lists for Fast Insertion and Removal
Data that support this recommendation
Comparison of Various Types of References

Use Indexed Attributes when Referencing by Attribute Value

Define your classes with indexed attributes when you need to find objects by attribute value. Indexed attribute searches are much faster than explicit searches, such as looping over a class, or implicit searches, such as if there exists ... such that statements.

For more information, see the references in this table:

For information on... See...
Indexed attributes
Using Indexed Attributes
Data that support this recommendation
Comparison of Various Types of References

Use Variables Only When Necessary

You should avoid using variables in your application unless you require one of these special features: data service, validity intervals, generic formulas, or backward chaining. Otherwise, use parameters or typed attributes. Variables introduce a significant performance overhead. Furthermore, obtaining the value of a variable by using a collect data statement is an asynchronous operation, which has implications on threading.

For more information, see the references in this table:

For information on... See...
Using variables to represent external data
Using Variables to Represent External or Simulated Data
Using the collect data statement
Implementing Multi-Threaded Procedural Processing and Wait States
Data that supports this recommendation
Comparison of Various Types of References

Try to Avoid Time-Based History Specifications

Always limit histories of variables and parameters. Whenever possible, limit histories by specifying the maximum number of data points as opposed to the maximum age of the history. History specifications based on time cause G2 to allocate memory at run-time, which can cause processing delays. Also, history references are slower than array references, so you might want to use arrays instead of histories in performance-critical applications.

For information on how to specify a history based on points, see Try to Avoid Time-Based History Specifications.

Do Not Transfer to Workspace Unless Necessary

When you dynamically create objects, you should only transfer them to a workspace if the application requires it. If the objects are going to be deleted again or if they are only used internally, you will improve performance by not transferring them to a workspace.

Procedural Processing

Use Inlining for Procedure Calls

If you practice good object-oriented design methodology by encapsulate knowledge, you will find that you will be creating a large number of methods and procedures. As a result, your application will also have a large number of procedure calls.

The trade-off for encapsulating knowledge and, thus, generating a large number of procedure calls is that your application will run less efficiently than if you had a fewer number of procedure calls. This is because each procedure call takes time to execute.

You can improve performance by minimizing the number of procedure and function calls. However, because combining separate procedures into one large procedure generally leads to less maintainable code, you should use this technique sparingly and only in performance-critical areas.

Instead, you can maintain reasonable functional divisions and avoid the performance penalty of call statements by using the inlining feature of procedures. Inlining a procedure improves performance by eliminating the overhead of calling the procedure.

When you declare a procedure to be inlineable, you must also declare it to be stable for dependent compilations.


Caution: If you distribute a text-stripped KB that declares procedures to be inlineable and you distribute an upgraded KB, the user of the KB will not be able to run the KB. For this reason, we recommend that you declare only private procedures to be inlineable when you distribute a text-stripped KB.

To declare a procedure to be inlineable:

You declare the item configuration for the procedure that is to be called by another procedure, not the calling procedure.

Because you can declare only individual procedures to be inlineable, you should focus your inlining on a relatively few procedures that the KB calls repeatedly.

When you declare the class hierarchy to be stable, G2 automatically declares any methods associated with the classes in the hierarchy to be inlineable.


Note: If you declare a procedure to be inlineable and the procedure is called recursively, G2 does not inline the procedure.

For more information, see the references in this table:

For information on... See...
Stability declarations
Use Stability Declarations
data that support this recommendation
Comparing Method, Procedure, and Function Calls

Minimize "On Error" Statements

Each time you enter the scope of an on error statement, your application experiences a performance penalty. In code that has been well written, it should not be necessary to use on error statements frequently.

Instead, you should confine the use on error statements to those errors that might occur when interacting with an external process, such as, writing to a file.

For more information, see the references in this table:

For information on... See...
Error signalling
Validating Arguments and Signalling Errors
Data that support this recommendation
Effect of On Error Statements

Use "Do in Parallel" to Interact with External Processes

When you need to make multiple calls to get data from an external process via G2 Gateway, use do in parallel loops such as:

The do in parallel statement allows G2 to group data requests, rather than completing independent "round trips" for each data request. This statement also allows the external process to work on fulfilling the data requests while G2 is simultaneously preparing further data requests or handling the information returned from earlier data requests.

Minimize Thread Interruption

You should try to avoid wait statements, allow other processing statements, and other statements that introduce wait states. Although the statements themselves execute quickly, because of the action of other threads, objects that exists before such statements might not exist directly after these statements. Thus, you must re-validate your data after each such statement, which can result in a significant performance penalty.

For more information, see Maintain Atomicity Whenever Possible.

Use System Procedures

G2 provides a utility module that contains numerous system procedures, which are built-in procedures and functions that implement common tasks. Some of the system procedures in sys-mod.kb are designed specifically to provide functionality that would be inefficient at the KB level, such as sorting and array operations. The efficiency gains are very significant, typically providing increased efficiency of a factor of ten. System procedures gain this advantage because they run as compiled code, rather than interpreted code.

For data that support this recommendation, see Effect of Using System Procedures.

Use Procedures Over Methods for Performance-Sensitive Code

Methods introduce approximately a 20% performance penalty over procedures when you invoke them by using a call statement. Thus, you might want to emphasize procedures over methods for performance-sensitive code. However, keep in mind that implementing behaviors as methods is a more object-oriented approach, which eases code development and maintenance.

Knowledge Bases

Consider Issues of Scalability

Throughout the course of designing and implementing your application, you should consider how your application will perform when it is scaled up to large problems. You should be aware of the fact that the fastest algorithm for small problems is not necessarily the fastest algorithm for large problems. In particular, you need to be aware of scalability issues when designing and implementing algorithms based on searches, sorts, matrix operations, or any algorithm that involves iteration. In such cases, you need to consider not only which algorithm is fastest for the current problem, but also which algorithm will perform best when applied to a much larger problem.

To determine how well a particular algorithm scales, you need to consider the number of operations that it requires given a large problem. You express the size of the problem in terms of the number of objects, the length of the list, and so on, which the algorithm processes iteratively. The number of operations is a function of the size of the problem, which you can call n. For example, if your algorithm involves two nested for loops, each with an iteration limit of n, the number of operations is proportional to n2, which is the number of times the algorithm processes the innermost statements.

For large values of n, the operation with the highest polynomial order will consume the most time, even if the same operation is not a bottleneck for small values of n. In these situations, you need to consider algorithms that will result in fewer operations given large values of n, if possible.

For example, to compare the contents of two lists, each with n elements, to see if they contain the same members, you could iterate through the items of the first list, and for each item, iterate through the second list to see if it is a member. This operation requires order(n2) operations, since for each n elements of the first list, you perform a search operation on the second list that is proportional to the size of the second list.

Alternatively, you could sort each list and then compare the first element of the first list with the first element of the second list, the second element of the first list to the second element of the second list, and so forth. In this way, you can carry out the sorting in order n log(n) operations (assuming you are using a good sorting algorithm), and you can carry out the element-by-element comparison in order(n) time, because the algorithm only requires one iteration. For large values of n, n2 >> n log(n) > n. Therefore, the second algorithm, which pre-sorts the lists, is more scalable than the simpler algorithm and is, therefore, preferable.

Bear in mind, however, that simple benchmarking for a given small value of n might not reveal the superiority of the second algorithm; in fact, it might show just the opposite. The reason is that the scalability analysis says nothing about the relative performance at small values of n. Nonetheless, using algorithm that scale well guarantees better performance when you apply your application to large-scale problems, which is when performance is most critical.

Separate Animation from Performance-Sensitive Code

Inside performance-sensitive code, do not cause the screen to redraw by moving objects, changing colors, charting or graphing, or placing attribute displays on objects whose attributes are being manipulated.

Hide Tables and Attribute Displays

To improve performance significantly, hide all tables and attribute displays. Also, you should realize that even if the workspace on which an item exists is not visible, you still incur a performance penalty if an item has an attribute display showing.

For data that support this recommendation, see Effects of Attributes Displays and Tables.

Minimize the Use of Displays

Use displays such as charts, trend charts, readouts, and dynamic displays only when users require them. Hide displays when not necessary and avoid showing more than one trend chart, chart, or free form table at a time. Update displays only when necessary, make the update intervals as long as possible, and stagger update intervals when multiple displays are visible. Finally, avoid unnecessary color changes. All of these actions have significant performance implications.

Avoid Scanned Rules and Polling Procedures

Avoid scanned rules and procedures that poll for events. Applications should be designed to be event driven.

For more information, see Avoid Scanning Rules and Avoid Polling.

Disable Rule Highlighting

As a diagnostic tool, G2 allows you to highlight rules when they execute. When this option is enabled, each rule that G2 invokes highlights for about 0.2 seconds, which is enough time to perceive the color change as a flash. During that time, no other activities take place within G2. Therefore, every rule that invokes when highlighting enabled takes up about 20% of the available CPU.

If you use this option during development, do not forget to disable it in the deployed KB.

To control rule highlighting:

Use Scheduled Drawing

Build your application to use the scheduled drawing mode. This mode allows G2 to group display update events efficiently and minimize the time taken to redraw the screen. Scheduled drawing is the default configuration of the Drawing Parameters system table.

To use scheduled drawing:

Statements

Minimize Conclude Statements

The conclude action is significantly slower than storing information in local variables. Try to use conclude only after the result of a calculation is determined and store intermediate results and working data in local variables and arrays.

For data that support this recommendation, see Example of Comparing Operations: Change Versus Conclude.

Avoid "Change the Text of" Expressions

Changing the text of a rule, procedure, or system-defined attribute requires G2 to parse text, which is a slow operation. Furthermore, you can only dynamically change the text of items when you are running G2 by using a development license.

G2 provides a much more efficient technique for changing the text of items, including all system-defined attributes, by using sequences and structures. You can use this technique to conclude text into items even in a run-time environment.

For more information, see Accessing System-Defined Attributes.

Avoid Existence and Type Checking

If possible, you should avoid statements that perform existence checking, whereby G2 checks whether a particular item exists in the KB, for example, if computer-1 exists... You should also try to avoid statements that perform type checking, whereby G2 verifies the data type of a particular value.

You can minimize the number of these statements by separating your modules into layers and avoiding wait states.

For more information, see the references in this table:

For more information on... See...
Separating modules into layers
Designing Reusable Modules
Avoiding wait states
Maintain Atomicity Whenever Possible

Beware of Statements that Cause Implicit Iteration

Try to avoid statements that implicitly cause iterations, particularly statements such as if there exists a class such that (logical expression), which forces G2 to examine all members of the given class to see if any meet the criterion in the expression. The exception to this rule are searches over indexed attributes. Although indexed attribute references use the same syntax, they do not require iterative search. Also try to avoid expressions that cause iterations over all items on a workspace, such as the item nearest to.

For more information, see the references in this table:

For more information on... See...
Using indexed attributes
Use Indexed Attributes when Referencing by Attribute Value
Data that support this recommendation
Comparison of Different Types of Global References

Be Careful with List Iterations

The efficiency of list iteration can vary tremendously, depending on the technique that you use. We do not recommend iterating over lists by using indices. References like X[100] are many times slower than the equivalent array reference because G2 counts through the list element-by-element until it reaches the 100th element.

Consider the following three algorithms for copying the elements of a list into an array:

Despite its complexity, the second approach is almost four times faster than the first approach for a list 5000 elements long. This benchmark emphasizes the importance of avoiding indexed references to list elements for long lists. The last approach, which does not require the insert and remove operations, executes six times faster than the second approach and 23 times faster than the first approach.

Iterations for each item or value in a list are most efficient when the value or item type cited in the iteration exactly matches the declared type of the list. For example, this iteration is most efficient when you declare MyList as an integer list:


Caution: G2 accesses list elements by looking for the first matching value. For example, if you have the list X = (0, 1, 2, 3, 4, 1), the statement remove X[5] from X results in X = (0, 2, 3, 4, 1). Because of the pre-evaluation of the indexed reference, the statement is equivalent to remove 1 from X. You can use additional syntax to remove elements from lists; however, list references still require element-by-element traversal.

For data that support this recommendation, see Comparison of Various Types of References.

Use Referencing Based on Relations in Performance-Sensitive Code

In performance-sensitive code, you should avoid connection referencing and, instead, construct and cache relations between connected objects to allow rapid traversals via the relation. This recommendation is based on the fact that references based on relations are approximately twice as fast references based on directed connections.

Also, if you are going to refer to items by their connections, create named ports or directed connections to improve efficiency.

For data that support this recommendation, see Comparison of Different Types of Global References.

Test Symbolic Equality with "=" Not "is"

Because is has many interpretations in G2 depending on the types of item and values involved, expressions involving is involve relatively high computational load. Do not use expressions like this:

Always use expressions like this:

Never use the outmoded "implied attribute" feature of G2, such as:

Always use expressions like this:

For data that support this recommendation, see Effect of Testing for Symbolic Equality.

| Prev | Next | Start of Chapter | Next Chapter | Contents | Glossary | Index | Comments | (4 out of 4)

Copyright © 1997 Gensym Corporation, Inc.