# initialise 'b' so that we always have valid values (and we don't
# forget it later on
set b "default"
# do something, eventually creating the variable 'a'
# now, set b to a if it exists
catch { set b $a}Well ... don't do it (or just use it when you are absolutely sure that it'll trigger only in very rare exceptions). I've attached a small test script which shows that this is approximately 10 times slower than:if {[info exists a]} {set b $a}when a does not exist. Only when the catch does not trigger, it is approximately 2 times faster than the if-variant.#!/bin/sh
# \
exec tclsh "$0" ${1+"$@"}
proc doinfo_fail {} {
set b "default"
for {set i 0} {$i < 1000000} {incr i} {
if {[info exists a]} {set b $a}
}
return $b
}
proc doinfo_ok_local {} {
set a "xxxxxxx"
set b "default"
for {set i 0} {$i < 1000000} {incr i} {
if {[info exists a]} {set b $a}
}
return $b
}
proc doinfo_ok_global {} {
global a
set b "default"
for {set i 0} {$i < 1000000} {incr i} {
if {[info exists a]} {set b $a}
}
return $b
}
proc docatch_fail {} {
set b "default"
for {set i 0} {$i < 1000000} {incr i} {
catch {set b $a}
}
return $b
}
proc docatch_ok_local {} {
set b "default"
set a "xxxxxxx"
for {set i 0} {$i < 1000000} {incr i} {
catch {set b $a}
}
return $b
}
proc docatch_ok_global {} {
global a
set b "default"
for {set i 0} {$i < 1000000} {incr i} {
catch {set b $a}
}
return $b
}
set a "xxxxxxx"
puts "doinfo_fail [time {doinfo_fail} 1]"
puts "doinfo_ok_local [time {doinfo_ok_local} 1]"
puts "doinfo_ok_global [time {doinfo_ok_global} 1]"
puts "docatch_fail [time {docatch_fail} 1]"
puts "docatch_ok_local [time {docatch_ok_local} 1]"
puts "docatch_ok_global [time {docatch_ok_global} 1]"
exit 0Donal Fellows replied: My performance figures for your script (using the current CVS HEAD version of Tcl under Solaris on an Ultra-5) can be seen below:
doinfo_fail 7957618 microseconds per iteration doinfo_ok_local 8789728 microseconds per iteration doinfo_ok_global 11462929 microseconds per iteration docatch_fail 67244385 microseconds per iteration docatch_ok_local 3634439 microseconds per iteration docatch_ok_global 5126791 microseconds per iterationWhat these demonstrate is that in performance-sensitive code it is important to code for the "normal" case; where you expect the code to normally succeed and only occasionally fail, there's actually quite a gain to be had from using catch, but you definitely take a performance hit in the failure case for doing this.
1 March 2001: Kevin Kenny adds:Let's try to quantify the effect a bit more. Let's take one of the procedures from Counting Elements in a List, and code it up with both info exists and catch.
proc count1 { list countArray } {
upvar 1 $countArray count
foreach item $list {
if { [catch { incr count($item) }] } {
set count($item) 1
}
}
return
}
proc count2 { list countArray } {
upvar 1 $countArray count
foreach item $list {
if { [info exists count($item)] } {
incr count($item)
} else {
set count($item) 1
}
}
return
}Now we can wrap a little benchmark program around the two procedures:puts { List | | [info }
puts { Length | [catch] | exists]}
puts { -------+----------+----------}
set list [list apple]
set i 1
while { $i <= 20 } {
set n [expr { 20000 / $i }]
set c1 [clock clicks -milliseconds]
for { set j 0 } { $j < $n } { incr j } {
count1 $list total
unset total
}
set t1 [expr { [clock clicks -milliseconds] - $c1 }]
set t1 [expr { $t1 / double($n) }]
set c2 [clock clicks -milliseconds]
for { set j 0 } { $j < $n } { incr j } {
count2 $list total
unset total
}
set t2 [expr { [clock clicks -milliseconds] - $c2 }]
set t2 [expr { $t2 / double($n) }]
puts [format " %6d | %8.3f | %8.3f " $i $t1 $t2]
flush stdout
incr i 1
lappend list apple
}This program tells us the time taken by both methods for various list lengths. THe following numbers are off a 550 MHz PIII running 8.3.2: List | | [info
Length | [catch] | exists]
-------+----------+----------
1 | 0.077 | 0.045
2 | 0.088 | 0.059
3 | 0.095 | 0.071
4 | 0.104 | 0.082
5 | 0.110 | 0.095
6 | 0.120 | 0.105
7 | 0.126 | 0.119
8 | 0.136 | 0.128
9 | 0.135 | 0.144
10 | 0.145 | 0.150
11 | 0.155 | 0.165
12 | 0.157 | 0.174
13 | 0.176 | 0.183
14 | 0.175 | 0.211
15 | 0.180 | 0.210
16 | 0.193 | 0.224
17 | 0.196 | 0.230
18 | 0.207 | 0.253
19 | 0.219 | 0.247
20 | 0.221 | 0.270 This is a pretty typical example of the behavior of [catch] versus [info exists] as the success-failure ratio goes up.A good rule of thumb is: If you expect the variable to exist at least 90% of the time, use [catch]. If the variable will fail to exist 10% of the time or more, use [info exists].Updated Performance Figures edit
DKF: These figures were all done on a MacBook Pro of around 2009 vintage.The take-home message from these figures is that it is always not a disadvantage to use info exists now that it is properly bytecoded (a Tcl 8.5 feature), and that a triggered catch is very expensive indeed (indeed, exceptionally so in 8.6).Tcl 8.4
Tcl 8.4.7, supplied with OSX Leoparddoinfo_fail 402842 microseconds per iteration doinfo_ok_local 408118 microseconds per iteration doinfo_ok_global 415020 microseconds per iteration docatch_fail 2232251 microseconds per iteration docatch_ok_local 114664 microseconds per iteration docatch_ok_global 110861 microseconds per iteration
Tcl 8.5
Tcl 8.5.2, built by ActiveStatedoinfo_fail 98248 microseconds per iteration doinfo_ok_local 110740 microseconds per iteration doinfo_ok_global 113167 microseconds per iteration docatch_fail 4862640 microseconds per iteration docatch_ok_local 112386 microseconds per iteration docatch_ok_global 113112 microseconds per iteration
Tcl 8.6
Development trunk tip from 27 March 2011.doinfo_fail 101400 microseconds per iteration doinfo_ok_local 115981 microseconds per iteration doinfo_ok_global 119663 microseconds per iteration docatch_fail 7382757 microseconds per iteration docatch_ok_local 183856 microseconds per iteration docatch_ok_global 178802 microseconds per iterationAPN presumes this performance hit with catch would also apply to (for example) looping control structures implemented in Tcl where catch is used to handle non-0 return codes such as continue and break


