Version 2 of fori

Updated 2004-12-31 23:29:21

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."

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 1}} {
     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} {
         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      47 microseconds
 #fori1 (1 .. 10):     result ==       55 in     297 microseconds
 #fori2 (1 .. 10):     result ==       55 in    3690 microseconds
 #fori3 (1 .. 10):     result ==       55 in     188 microseconds
 #fori4 (1 .. 10):     result ==       55 in     140 microseconds
 #fori5 (1 .. 10):     result ==       55 in     147 microseconds 

 #for   (1 .. 100):    result ==     5050 in      75 microseconds
 #fori1 (1 .. 100):    result ==     5050 in    3605 microseconds
 #fori2 (1 .. 100):    result ==     5050 in    1413 microseconds
 #fori3 (1 .. 100):    result ==     5050 in     933 microseconds
 #fori4 (1 .. 100):    result ==     5050 in     559 microseconds
 #fori5 (1 .. 100):    result ==     5050 in     521 microseconds

 #for   (1 .. 1000):   result ==   500500 in    5557 microseconds
 #fori1 (1 .. 1000):   result ==   500500 in   11434 microseconds
 #fori2 (1 .. 1000):   result ==   500500 in    8427 microseconds
 #fori3 (1 .. 1000):   result ==   500500 in    6787 microseconds
 #fori4 (1 .. 1000):   result ==   500500 in    5049 microseconds
 #fori5 (1 .. 1000):   result ==   500500 in    1546 microseconds

 #for   (1 .. 10000):  result == 50005000 in    6567 microseconds
 #fori1 (1 .. 10000):  result == 50005000 in   55847 microseconds
 #fori2 (1 .. 10000):  result == 50005000 in   57480 microseconds
 #fori3 (1 .. 10000):  result == 50005000 in   40697 microseconds
 #fori4 (1 .. 10000):  result == 50005000 in   22051 microseconds
 #fori5 (1 .. 10000):  result == 50005000 in   17589 microseconds

Out of these fori5 looks the best. In the 1-1000 range it is ~3x faster than for, and in the 1-10000 range it is only ~3x slower.

Please, feel free to add improved versions.