Performance Improvement and Script Optimization

The Tcl/Tk language support in HyperWorks Desktop allows you to perform many complex operations by developing Tcl based procedures. While this flexibility provides the means to develop a wide range of solutions, there are some performance issues associated with graphics operations when interacting with HyperWorks Desktop. These issues arise due to the time required to perform actions including displaying messages, changing of the mouse cursor, and the pre-highlighting of entities during mark operations.

Along with the Tcl performance tips discussed below, the following HyperWorks Desktop based commands can be used to overcome some of these limitations:
*evaltclstring string 0
*evaltclscript proc_name 0

*retainmarkselections <state>
*setoption entity_highlighting=<state>
*setoption command_file_state=<state>
*setoption block_messages=<state>
hwbrowsermanager view flush state

These last five should be reset when leaving a Tcl script as these can effect the user interaction with HyperWorks Desktop.

Tcl scripts should be written in a way that aids efficient execution. Based on research related to Tcl/Tk perfomance, a test was written comparing different approaches.

The times listed below are based on using the Tcl time command using 10000 iterations, on a 266mhz Pentium based computer.

Variable assignments outside of the current scope

Variables defined in a procedure are local by default. There are a number of methods available to access a variable that is outside the current scope of a procedure, including using the commands global, upvar, uplevel, and using the global namespace reference ::
uplevel w/level: 66 microseconds per iteration
uplevel: 55 microseconds per iteration
upvar: 55 microseconds per iteration
global: 60 microseconds per iteration
global namespace: 50 microseconds per iteration
local: 39 microseconds per iteration

Variable addition and subtraction

When adding or subtracting a value from a variable, when applicable, use the incr command, instead of an expr call.
expr: 55 microseconds per iteration
incr: 11 microseconds per iteration
difference: 5.00x

Braced expression calls

When using the expr command, place braces around the expression to be evaluated to take advantage of byte code compiling.
w/o braces: 143 microseconds per iteration
w/ braces 55 microseconds per iteration
difference: 2.60x

Returning from a procedure

Avoid using the return function in a procedure when possible. Setting a variable to the value to return, and calling set varname at the bottom of the procedure is more efficient.
return: 39 microseconds per iteration
set 5 microseconds per iteration
difference: 7.80x

List Parsing

When parsing the contents of a list, the fastest method is to use the foreach command. The next best choice would be to use a while loop with a decreasing index test, and the least efficient method is to use a for loop.
for/lindex: 159 microseconds per iteration
while/lindex: 115 microseconds per iteration
foreach: 66 microseconds per iteration

Variable initialization

When initializing global variables, place them inside a procedure, and access them in the global namespace to take advantage of byte code compiling.
inline: 110000 microseconds
proc based: 50000 microseconds

Arrays vs. lists

For large list searches, it may be more efficient to use arrays. List searches are sequential searches and may not be very efficient for large lists. Instead of adding items to a list, set the indices of an array to the item name.

Using a list:
set a "1 2 3 4 5 6 7 8 9 0";
if {[lsearch $a 9] != -1} {
   …command body…
}
Using an array:
set a(1) 1;
set a(2) 2;
…
set a(9) 9;
set a(0) 0;

if {[info exist a(9)] == 1} {
   …command body…
}

For sorted lists, it may be more efficient to use the -sorted option to lsearch, along with -ascii, -decreasing, -dictionary, -increasing, -integer, and/or -real, depending on the list type.

Example

The following example shows procedures to sum the numbers from a global list, and demonstrates some of the performance enhancements discussed above.
set vals {10 20 30 40 50 60 70 80 90};

proc sum {} {
   global vals;
   set sum 0;
   for { set i 0 } { $i < [ llength $vals ] } { incr i } {
      set curVal [ lindex $vals $i ];
      set sum [ expr $sum + $curVal ];
   }
   return $sum;
}

proc fasterSum {} {    
   set sum 0;
   foreach val $::vals {
      incr sum $val;
   }
   set sum;
}

sum:  829
fasterSum:  83
difference:  9.99x