Ticol Tcl - Tips and Tricks

Improving Performance

Ticol is a pure interpreted script language. It does not perform any byte-code compilation or dynamic optimisation, so performance will be slower
than other Tcl variants such as ActiveState. However, since Ticol was designed for day-to-day administrative script work this should not present
any serious problems If performance is an issue then it is recommended to use another such as ActiveState Tcl

The Ticol Macro PreProcessor (MPP) is able to statically optimise some  aspects of the script source code but the result will still be interpreted
The MPP strips out comments, so they are not interpreted as commands by Ticol

Loop-processing constructs will not be as efficient as a compiled language such as C#. however many of the more complex list-processing or string-
splitting operations are quite efficient. Ticol offers a wide range of commands and some plugin libraries which are "closer to C++" and therefore
fast and efficient

You should avoid single-character loop-processing within Tcl, particularly for loops which require  complex branched if statements.

Preferably call a dedicated, external DLL function and use Ticol as the 'glue' to hold together more efficient components. A simple plugin
interface library is offered for C++ programmers.

The following commands are offered to enhance performance:

  • array item. Ambiguous addressing of arrays with default return instead of error
  • calc. Static, self-optimising equivalent to [expr]
    Translates expressions into Tcl commands in Polish Notation
  • chr. Return a character by number with optional offset
  • cmp. Efficient compare function
  • comma. Efficient comma-formatting of numbers
  • concat. Efficient string concatenation
  • eq, eqi Efficient comparison commands
  • eqn, nen Shorthand equivalence/disequivalence
  • funct. Call any expr function directly
  • ge, gei *i is the case insignificant version ...
  • gt, gti Greater than (gti:ignore case)
  • index. Rapid single char index into a string
  • is_mod. Modular arithmetic test
  • le, lei Less than or equal (ignore case)
  • lt, lti Less than (lei:ignore case)
  • makestr. Create a new string
  • ne, nei Not equal (nei:ignore case)
  • rset. Assign to a string and align
  • setat. String manipulation
  • store. Very rapid, buffered memory storage
  • strsplit. Split a string
  • strstr. Locate a substring
  • tohex. Convert an integer to hexadecimal

Move proc definition, list evaluation and creation outside of loops if possible. Pre-evaluate lists into variables. Make full use of the macro-PreProcessor to optimise constants during the preprocessing phase Use [concat] when concatenating large strings. Avoid chaining the set operator as the accumulated string passes through the interpreter

    POOR:

    set out $out$ch

    GOOD:

    concat out $ch

Use [cmp], this compares string segments in memory, instead of extracting and then comparing the resulting extract in an expression. It can
potentially avoid copying large blocks of data. 'time' tests show it to be around 30% faster especially within loops

    POOR:

    if { eq [mid $data $i 2] "Hi" } {

    GOOD:

    if { cmp data "Hi" $i 2 } {

When extracting small sections of a string use [setat] to directly slice the section of a string to a variable, thus avoiding the use of the set call. This sets a variable directly with index values located at a specified position and length. Timing tests show [setat] is around 50% faster than using [mids] together with [set]

    POOR:

    set ch [mids $data $i 1]

    GOOD:

    setat ch $data $i 1

Character replacement can be performed rapidly using [replacechar] rather than re-assigning the result of [string replace]

    POOR:

    set s [readfile ticol.man]
    set s [string replace $s "\t" " "]

    GOOD:

    set s [readfile ticol.man]
    replacechar s "\t" " "

To concatenate strings, particularly large ones or large numbers of small ones use [append] instead of repeated calls to chained [set] commands. The use of [set] chains creates multiple copies of strings whereas append appends directly to the original variable. Append is faster than [lappend] and [store] is about 5 times faster than [append] but less flexible to use

    POOR:

    set a "The quick"
    set a "$a brown fox"
    set a "$a jumped over"
    set a "$a the lazy dog"

    GOOD:

    set a "The quick"
    append a " brown fox"
    append a " jumped over"
    append a " the lazy dog"

    BEST:

    store "The quick"
    store " brown fox"
    store " jumped over"
    store " the lazy dog"
    set a [store get]

A key to interpreter performance is pre-calculating as much as possible outside of intensive loops and making good use of intrinsic functions instead of synthesising them from other commands

String comparison for string prefixes should use the [eqn] and [nen] commands which will safely test equality or inequality for a given length. This would be effective in tight loops when comparing large string prefixes than using a compound of [eq] and [string]

    POOR:


    set a "The quick brown fox"
    if { eq [string left $a 3] "The" } { do something ... }

    GOOD:

    set a "The quick brown fox"
    if { eqn $a The 3 } { do something ... }

Use [for]. For loops are often far more efficient than while {} loops

For accurate performance timing, use the 'time' command or run Ticol in "graph" mode with the /G command line argument [let] may be used for multiple, simultaneous assignments

Use [array_foreach] to iterate arrays if you need simple behaviour

Because it is a generalised expression-handler with significant checking-overheads, [expr] may be far slower than decomposing an expression into dedicated component operations with Tcl Polish 'prefix' syntax. The Macro PreProcessor will optimise the [calc] command and expand into raw Tcl statements. If the MPP is disabled then [calc] will be interpreted equivalent to [expr]

Example:

    set src [expr ($x & $y) % 3] # Expression
    set src [% [& $x $y] 3]          # Decomposed expression (~50% faster)
    set src [calc ($x & $y) % 3] # Expression is optimised by the MPP

See:  http://wiki.tcl.tk/348

Move command calls out of loop arguments and bodies where possible to avoid repeated calls to slower evaluation commands

Example:

    option expression off
    for {set i 1} {< $i [linkedlist count $handle]} {++ i} { # Slower

    set c [linkedlist count $handle]   # Evaluate once
    for {set i 1} {< $i $c} {++ i} {        # Faster

Top


Debugging

To run in breakpoint/debugging mode use ticol.exe <scriptname> /bp or, in the code, use [option breakpoint on]

When a [halt] or error condition arises the debugger menu will launch

Consider the following code...

cls
option expression off
option breakpoint on
watch add i
halt

set i 0
for {} {< $ 10} {++ i} {
	if {> $i 5} {
		puts "Break condition met at i==$i"
		break
	}
	halt
}
newline
puts "Exit for with i==$i"
puts "* Done *"

We can run this using:   ticol.exe <scriptname> or ticol.exe <scriptname> /NA to avoid running the autoexec.tcl script

On running the debugger will be enabled and will halt at the command after first [halt] instruction
Note that the [halt] instruction itself is not traced. Thus we halt at the first [set] command
Note also that the 'local' line number is show on the left. This is the line number of the snippet of code being executed

debug-1.png (1641 bytes)

Variable i is enabled for a [watch] statement and is curently out of scope

Pressing [R] to run will cause execution to resume and then halt again and break at the first command after the next [halt] statement ...

debug-2.png (2769 bytes)

Pressing [R] again will run another iteration of the [for] loop and now, variable i is in scope and traces the value 0 ...

debug-3.png (3045 bytes)

Pressing [R] repeatedly will resume and run each iteration of the [for] loop and, in this example, will trace the value of variable i ...

debug-4.png (3796 bytes)

Pressing [R] repeatedly will evetually result in the loop exiting when variable i is greater than 5

So far we have used [R] to run to the next [halt] statement. Alternatively, the debugger can single-step with either [T] to trace showing the stack or  [N] to simply step to and execute the next instruction. The image below shows two [T] trace steps ...

debug-trace.png (8244 bytes)

Pressing [N] for next, skips all debugging information other than variable watch (if this is enabled) ...

debug-next.png (3408 bytes)

[W] will toggle Watch off or on, unless overridden by the [watch] command. See help watch.

The script may programatically abandon debugging by either clearing any [halt] command or issuing an [option breakpoint off] command

Top


Tips and Tricks

A selection of random tips:

  • If a program exits loops or tests unexpectedly or loops 'hang' then you've probably used the wrong [option expression] setting
  • You can load a program into the CLI using:   load <scriptname>
    The loaded program can be *.tcl or *.tcx
    You can inspect the loaded/macro-processed script using:   dump
    You can run the loaded script using:  run
  • The [run] command will allow a script to be loaded and executed with command-line arguments in one step
    e.g.  run foo.tcl 1 2 3
  • Self-elevating script can be constructed by using [is_elevated] and [elevate]
  • To debug, you will need a [halt] command or an error condition.
    To run in breakpoint/debugging mode use ticol.exe <scriptname> /bp or, in the code, use [option breakpoint on]
  • Avoid dereferencing a variable containing a string which will execute Tcl code when testing for non-empty with [if]
    Use:  if{is_empty var} {...
  • Use file-level modularity to break up scripts and avoid huge, monolithic script files
        Break big scripts into smaller files and call using [eval scriptname]
        Be aware that each call to [eval scriptname] will evoke the macro
        preprocessor (MPP) which has a time cost
        Arguments to external files are permissible via [eval scriptname]
  • Command line help is available at the console using:   ticol.exe /?
  • Topic help is available from the Ticol command-line using:   help <topic>
  • If you're not sure of the topic use find, e.g.:  find string
  • Unlike ActiveState Tcl, results echo is not enabled by default in Ticol
    Use:   option echo on   to enable this
  • An enhancement over [puts] and [format] is [printf] which works similarly to the C/C++ printf()
  • You can use ticol.exe as an expression evaluator for batch scripts
    Use the /EXPR command-line argument (See: help /expr)
    e.g.   ticol.exe /expr:22.0/7.0
  • Creating obfuscated TCX scripts is easy. Just use:   ticol.exe <scriptname> /C
    You can't encode a TCX file though
  • Although [expr] will attempt to apply BEDMAS rules, you are recommended to make full use of brackets in expressions
  • Iterating arrays is quicker using [array foreach] than [array get] with [foreach]
  • Always wrap command arguments in braces. Avoid the temptation to omit braces
    e.g. use [if {condition}] {code}   and not [if condition] code
  • You can initialise using data from another file by using [eval scriptname]
  • You can profile script performance and spot bottlenecks using the /G command-line option
    Use:   ticol.exe <scriptname> /G    or, preferably:   ticol.exe <scriptname> /G /NA to ignore autoexec.tcl
  • [is_mod] offers a simpler and quicker way to take actions depending on a mod result
  • [goto_block] must have any initialisers set within a dummy entry
  • [foreach] requires the initial arguments to be braced:
    Use: foreach {x y} [array get env] { puts $x=$y }
    not: foreach x y [array get env] { puts $x=$y }
  • Strings with C-like escape sequences don't work from the CLI. CLI interaction with Windows requires C-escapes to be disabled
    You can enable escapes within strings if you set the following options:
    option autoexec off
    option escapes on
  • If a script halts or exits for no apparent reason use [option echo on], make full use of [catch] and [try catch], use the debugger to single-step by adding /BP to ticol.exe command line or using [option breakpoint on] and, if necessary, a [halt] statement before the suspected error
  • The Macro Preprocessor doesn't replace macro variables within quoted strings
  • A common Macro Preprocessor error is to comment out an if statement as #if. This is a macro command.
    It is good practice to leave a space after any # comment character
  • Return from a child script to the parent using [return -code exit]
  • [day_of_week] works only with the Gregorian calendar
  • A variable name may contain a space but must be quoted.
    A variable name may also be one or more spaces!
    To retrieve a variable name comprised only of spaces you must use braces as follows

    set
    " " hello
    puts ${ }
  • A procedure name may be empty but to call this you must use [call]
    proc {} {} {puts hi}    # Procedure with no name
    call {}
  • Ticol can do lambda-like operations
    For more information see:  help lambda
  • You can make a new array const by appending -const to the array declaration
    array foo { ... initialisers ...} -const
  • [upvar] allows a shortcut where the local variable name is omitted. In this case the local name will be the same as the referenced name
    If name conflicts arise you can still override by specifying a local name. The local name can be different
  • PERL-style multiple-dereferencing is supported using the dollar sign [set [set [set a]]]   is the same as $$$a
  • If you run into complexities using escape (\) characters, you can use [escape] and [unescape] to help
  • You can comment out blocks of code using several methods. The macro and "C" long-comment are the most efficient since the MPP will excise the unreferenced code
    Use C/C++ style /* ... */ long comments
    Use if {0} { ... }
    Use #ifdef with a macro-variable which has not yet been set, e.g. #ifdef comment
    Use a [goto_block] with a [goto] 'skip' command
  • Short # comments don't need a semi-colon prefix such as ->  ;# Comment
  • Source code line numbers can be entered into a script using the #__LINE__ macro
  • [assert] is very useful during development of a script
    It can be globally disabled using #define NDEBUG as in C/C++
  • You can set argc and $argv() consts using the following method (add -nocomplain if you rerun the code in the IDE)
    unset argc -const -nocomplain
    unset argv -const -nocomplain
    set argc 1
    set argv(0) Hello
  • The Macro PreProcessor (MPP) supports a wide range of commands including #if, #exec, #echo and #exit
    The #if command takes a Tcl expression and will spawn a separate interpreter instance during MPP preload
    Note that macro commands are all fully executed before a script is run

    #if eq $env(computername) TORNADO
    #echo Running on TORNADO
    #else
    #echo Not running on TORNADO
    #endif
  • You can configure the maximum recursion level via ticol.ini using

    [config]
    RecursionMax=<value>


    Where <value> is an integer between 1 and 5000. The default is 5000 and a good value would be 1000
  • A global struct can be referenced from a passed name in a proc using [struct set ::${structvar}.field] as follows
    proc foo {x} {
        upvar $x      # Not strictly required to reference a global; use the scope prefix ::
        puts [type $x]
        puts "In proc foo: Global s.a is: '[struct set ::${x}.a]'"
        struct set ${x}.a Goodbye
        puts "Exit proc foo: Global s.a is: '[struct set ::${x}.a]'"
    }
    struct s {a 10}
    foo s
  • Ticol uses the @ sign not # sign in [upvar] and [uplevel] to specify an absolute level
    This simplifies handling of comments and to a degree, makes more syntactic sense
    uplevel @0 set a 1
    upvar @0 s
  • You can check at runtime if your current script is encrypted using [info encrypted]
  • If you find you cannot run Windows commands such as dir, check the option autoexec setting using [option]
    Enable using:   option autoexec on
  • Braced variables are dereferenced recursively with the innermost braces resolved first. This allows complex multi-level dereferencing
    The opening brace must follow immediately after the dollar $ sign
    Spaces are within curly-braces are treated as part of the variable name and a variable name may be one or more spaces
    set a 1
    set b1 2
    set c2 3
    ${a}                      # -> a -> 1
    ${b{$a}}              #-> a -> b1 -> 2
    ${c${b${a}}}      # -> a -> b1 -> c2 -> 3
    set " " "Space!"
    ${ }
                          # -> Space!
  • Cleanly reset background colour highlighting in a text without it wrapping onto the next line by suppressing CRLF before calling [textcolor]

    textcolor
    white darkmagenta
    puts " * Hello world * " -nonewline
    textcolor
    newline

See the manual FAQ section for more tips and tricks

Top


Back | Top