MC, 31 Dec 2004: In a post on his blog[L1 ] discussing implementation and design issues of his Ramble game (written, naturally in Tcl/Tk), WHD proposes a fori command, whose signature looks like:
fori var from to script
Since any Tcl version is going to be slower than the built-in version of [for], Will writes: "I think something like fori would be a nice addition to Tcl...but it would have to be implemented as a C extension."
I was curious how much slower a Tcl version would be... Here are a couple of different approaches I came up with this afternoon:
proc fori1 {var m n script} { uplevel 1 for [list "set [list $var] $m"] [list "\$[list $var] <= $n"] [list "incr [list $var]"] [list $script] } proc fori2 {var m n script} { uplevel 1 for [list "set [list $var] $m"] [list "\[set [list $var]\] <= $n"] [list "incr [list $var]"] [list $script] } proc fori3 {var m n script} { uplevel 1 "set [list $var] [expr {$m - 1}] while {\[incr [list $var]\] <= {$n}} { $script }" } proc fori4 {var m n script} { uplevel 1 "if {1} { for {set [list $var] {$m}} {\$[list $var] <= {$n}} {incr [list $var]} { $script } }" } proc fori5 {var m n script} { uplevel 1 "if {1} [list "set [list $var] [expr {$m - 1}] while {\[incr [list $var]\] <= {$n}} { $script }"]" }
and now a bit of infrastructure to make testing/timing them easier:
proc try {name proc limit {n_iterations 50}} { set time [time {set result [$proc $limit]} $n_iterations] return [format { #%-5s %-14s result == %8d, in %7d microseconds} \ $name "(1 .. $limit):" $result [lindex $time 0]] } proc builtin {limit} { set sum 0 for {set i 1} {$i <= $limit} {incr i} { incr sum $i } return $sum } for {set i 1} {$i <= 5} {incr i} { proc method$i {limit} [format { set sum 0 fori%d i 1 $limit {incr sum $i} return $sum } $i] } foreach size {10 100 1000 10000} { if {$size != 10} then {puts ""} foreach version {builtin method1 method2 method3 method4 method5} \ name {for fori1 fori2 fori3 fori4 fori5} { # run once before we begin timing $version $size puts [try $name $version $size] } }
and now to see the timing results (these are from tclsh 8.4.7 on a 1.33GHz PowerBook running MacOS X 10.3):
#for (1 .. 10): result == 55, in 795 microseconds #fori1 (1 .. 10): result == 55, in 440 microseconds #fori2 (1 .. 10): result == 55, in 1242 microseconds #fori3 (1 .. 10): result == 55, in 351 microseconds #fori4 (1 .. 10): result == 55, in 302 microseconds #fori5 (1 .. 10): result == 55, in 359 microseconds #for (1 .. 100): result == 5050, in 79 microseconds #fori1 (1 .. 100): result == 5050, in 1193 microseconds #fori2 (1 .. 100): result == 5050, in 1182 microseconds #fori3 (1 .. 100): result == 5050, in 2423 microseconds #fori4 (1 .. 100): result == 5050, in 567 microseconds #fori5 (1 .. 100): result == 5050, in 451 microseconds #for (1 .. 1000): result == 500500, in 651 microseconds #fori1 (1 .. 1000): result == 500500, in 52015 microseconds #fori2 (1 .. 1000): result == 500500, in 12533 microseconds #fori3 (1 .. 1000): result == 500500, in 8659 microseconds #fori4 (1 .. 1000): result == 500500, in 4527 microseconds #fori5 (1 .. 1000): result == 500500, in 3365 microseconds #for (1 .. 10000): result == 50005000, in 8012 microseconds #fori1 (1 .. 10000): result == 50005000, in 258911 microseconds #fori2 (1 .. 10000): result == 50005000, in 260356 microseconds #fori3 (1 .. 10000): result == 50005000, in 114350 microseconds #fori4 (1 .. 10000): result == 50005000, in 19956 microseconds #fori5 (1 .. 10000): result == 50005000, in 15729 microseconds
Out of these fori5 looks the best overall, but it is still 2-5 times slower than for is; can we do any better?