libbench.tcl

Go to the documentation of this file.
00001 /*  -*- tcl -*-*/
00002 /*  libbench.tcl ?(<option> <value>)...? <benchFile>...*/
00003 /* */
00004 /*  This file has to have code that works in any version of Tcl that*/
00005 /*  the user would want to benchmark.*/
00006 /* */
00007 /*  RCS: @(#) $Id: libbench.tcl,v 1.3 2007/08/21 20:02:21 andreas_kupries Exp $*/
00008 /* */
00009 /*  Copyright (c) 2000-2001 Jeffrey Hobbs.*/
00010 /*  Copyright (c) 2007      Andreas Kupries*/
00011 /* */
00012 
00013 /*  This code provides the supporting commands for the execution of a*/
00014 /*  benchmark files. It is actually an application and is exec'd by the*/
00015 /*  management code.*/
00016 
00017 /*  Options:*/
00018 /*  -help               Print usage message.*/
00019 /*  -rmatch <regexp-pattern>    Run only tests whose description matches the pattern.*/
00020 /*  -match  <glob-pattern>  Run only tests whose description matches the pattern.*/
00021 /*  -interp <name>      Name of the interp running the benchmarks.*/
00022 /*  -thread <num>                 Invoke threaded benchmarks, number of threads to use.*/
00023 /*  -errors <boolean>             Throw errors, or not.*/
00024 
00025 /*  Note: If both -match and -rmatch are specified then _both_*/
00026 /*  apply. I.e. a benchmark will be run if and only if it matches both*/
00027 /*  patterns.*/
00028 
00029 /*  Application activity and results are communicated to the highlevel*/
00030 /*  management via text written to stdout. Each line written is a list*/
00031 /*  and has one of the following forms:*/
00032 /* */
00033 /*  __THREADED <version>     - Indicates threaded mode, and version*/
00034 /*                             of package Thread in use.*/
00035 /* */
00036 /*  Sourcing {<desc>: <res>} - Benchmark <desc> has started.*/
00037 /*                             <res> is the result from executing*/
00038 /*                             it once (compilation of body.)*/
00039 /* */
00040 /*  Sourcing <file>          - Benchmark file <file> starts execution.*/
00041 /* */
00042 /*  <desc> <res>             - Result of a benchmark.*/
00043 /* */
00044 /*  The above implies that no benchmark may use the strings 'Sourcing'*/
00045 /*  or '__THREADED' as their description.*/
00046 
00047 /*  We will put our data into these named globals.*/
00048 
00049 global BENCH bench
00050 
00051 /*  'BENCH' contents:*/
00052 /* */
00053 /*  - ERRORS  : Boolean flag. If set benchmark output mismatches are*/
00054 /*              reported by throwing an error. Otherwise they are simply*/
00055 /*              listed as BAD_RES. Default true. Can be set/reset via*/
00056 /*              option -errors.*/
00057 /* */
00058 /*  - MATCH   : Match pattern, see -match, default empty, aka everything*/
00059 /*              matches.*/
00060 /* */
00061 /*  - RMATCH  : Match pattern, see -rmatch, default empty, aka*/
00062 /*              everything matches.*/
00063 /* */
00064 /*  - OUTFILE : Name of output file, default is special value "stdout".*/
00065 /*  - OUTFID  : Channel for output.*/
00066 /* */
00067 /*  The outfile cannot be set by the caller, thus output is always*/
00068 /*  written to stdout.*/
00069 /* */
00070 /*  - FILES   : List of benchmark files to run.*/
00071 /* */
00072 /*  - ITERS   : Number of iterations to run a benchmark body, default*/
00073 /*              1000. Can be overridden by the individual benchmarks.*/
00074 /* */
00075 /*  - THREADS : Number of threads to use. 0 signals no threading.*/
00076 /*              Limited to number of files if there are less files than*/
00077 /*              requested threads.*/
00078 /* */
00079 /*  - EXIT    : Boolean flag. True when appplication is run by wish, for*/
00080 /*              special exit processing. ... Actually always true.*/
00081 /* */
00082 /*  - INTERP  : Name of the interpreter running the benchmarks. Is the*/
00083 /*              executable running this code. Can be overridden via the*/
00084 /*              command line option -interp.*/
00085 /* */
00086 /*  - uniqid  : Counter for 'bench_tmpfile' to generate unique names of*/
00087 /*              tmp files.*/
00088 /* */
00089 /*  - us      : Thread id of main thread.*/
00090 /* */
00091 /*  - inuse   : Number of threads active, present and relevant only in*/
00092 /*              threaded mode.*/
00093 /* */
00094 /*  - file    : Currently executed benchmark file. Relevant only in*/
00095 /*              non-threaded mode.*/
00096 
00097 /* */
00098 /*  'bench' contents.*/
00099 
00100 /*  Benchmark results, mapping from the benchmark descriptions to their*/
00101 /*  results. Usually time in microseconds, but the following special*/
00102 /*  values can occur:*/
00103 /* */
00104 /*  - BAD_RES    - Result from benchmark body does not match expectations.*/
00105 /*  - ERR        - Benchmark body aborted with an error.*/
00106 /*  - Any string - Forced by error code 666 to pass to management.*/
00107 
00108 /* */
00109 /*  We claim all procedures starting with bench**/
00110 /* */
00111 
00112 /*  bench_tmpfile --*/
00113 /* */
00114 /*    Return a temp file name that can be modified at will*/
00115 /* */
00116 /*  Arguments:*/
00117 /*    None*/
00118 /* */
00119 /*  Results:*/
00120 /*    Returns file name*/
00121 /* */
00122 ret  bench_tmpfile () {
00123     global tcl_platform env BENCH
00124     if {![info exists BENCH(uniqid)]} { set BENCH(uniqid) 0 }
00125     set base "tclbench[incr BENCH(uniqid)].dat"
00126     if {[info exists tcl_platform(platform)]} {
00127     if {$tcl_platform(platform) == "unix"} {
00128         return "/tmp/$base"
00129     } elseif {$tcl_platform(platform) == "windows"} {
00130         return [file join $env(TEMP) $base]
00131     } else {
00132         return $base
00133     }
00134     } else {
00135     # The Good Ol' Days (?) when only Unix support existed
00136     return "/tmp/$base"
00137     }
00138 }
00139 
00140 /*  bench_rm --*/
00141 /* */
00142 /*    Remove a file silently (no complaining)*/
00143 /* */
00144 /*  Arguments:*/
00145 /*    args  Files to delete*/
00146 /* */
00147 /*  Results:*/
00148 /*    Returns nothing*/
00149 /* */
00150 ret  bench_rm (type args) {
00151     foreach file $args {
00152     if {[info tclversion] > 7.4} {
00153         catch {file delete $file}
00154     } else {
00155         catch {exec /bin/rm $file}
00156     }
00157     }
00158 }
00159 
00160 /*  bench --*/
00161 /* */
00162 /*    Main bench procedure.*/
00163 /*    The bench test is expected to exit cleanly.  If an error occurs,*/
00164 /*    it will be thrown all the way up.  A bench proc may return the*/
00165 /*    special code 666, which says take the string as the bench value.*/
00166 /*    This is usually used for N/A feature situations.*/
00167 /* */
00168 /*  Arguments:*/
00169 /* */
00170 /*    -pre  script to run before main timed body*/
00171 /*    -body script to run as main timed body*/
00172 /*    -post script to run after main timed body*/
00173 /*    -ipre script to run before timed body, per iteration of the body.*/
00174 /*    -ipost    script to run after timed body, per iteration of the body.*/
00175 /*    -desc message text*/
00176 /*    -iterations   <#>*/
00177 /* */
00178 /*  Note:*/
00179 /* */
00180 /*    Using -ipre and/or -ipost will cause us to compute the average*/
00181 /*    time ourselves, i.e. 'time body 1' n times. Required to ensure*/
00182 /*    that prefix/post operation are executed, yet not timed themselves.*/
00183 /* */
00184 /*  Results:*/
00185 /* */
00186 /*    Returns nothing*/
00187 /* */
00188 /*  Side effects:*/
00189 /* */
00190 /*    Sets up data in bench global array*/
00191 /* */
00192 ret  bench (type args) {
00193     global BENCH bench errorInfo errorCode
00194 
00195     # -pre script
00196     # -body script
00197     # -desc msg
00198     # -post script
00199     # -ipre script
00200     # -ipost script
00201     # -iterations <#>
00202     array set opts {
00203     -pre    {}
00204     -body   {}
00205     -desc   {}
00206     -post   {}
00207     -ipre   {}
00208     -ipost  {}
00209     }
00210     set opts(-iter) $BENCH(ITERS)
00211     while {[llength $args]} {
00212     set key [lindex $args 0]
00213     switch -glob -- $key {
00214         -res*   { set opts(-res)  [lindex $args 1] }
00215         -pr*    { set opts(-pre)  [lindex $args 1] }
00216         -po*    { set opts(-post) [lindex $args 1] }
00217         -ipr*   { set opts(-ipre)  [lindex $args 1] }
00218         -ipo*   { set opts(-ipost) [lindex $args 1] }
00219         -bo*    { set opts(-body) [lindex $args 1] }
00220         -de*    { set opts(-desc) [lindex $args 1] }
00221         -it*    {
00222         # Only change the iterations when it is smaller than
00223         # the requested default
00224         set val [lindex $args 1]
00225         if {$opts(-iter) > $val} { set opts(-iter) $val }
00226         }
00227         default {
00228         error "unknown option $key"
00229         }
00230     }
00231     set args [lreplace $args 0 1]
00232     }
00233     if {($BENCH(MATCH) != "") && ![string match $BENCH(MATCH) $opts(-desc)]} {
00234     return
00235     }
00236     if {($BENCH(RMATCH) != "") && ![regexp $BENCH(RMATCH) $opts(-desc)]} {
00237     return
00238     }
00239     if {$opts(-pre) != ""} {
00240     uplevel \#0 $opts(-pre)
00241     }
00242     if {$opts(-body) != ""} {
00243     # always run it once to remove compile phase confusion
00244     if {$opts(-ipre) != ""} {
00245         uplevel \#0 $opts(-ipre)
00246     }
00247     set code [catch {uplevel \#0 $opts(-body)} res]
00248     if {$opts(-ipost) != ""} {
00249         uplevel \#0 $opts(-ipost)
00250     }
00251     if {!$code && [info exists opts(-res)] \
00252         && [string compare $opts(-res) $res]} {
00253         if {$BENCH(ERRORS)} {
00254         return -code error "Result was:\n$res\nResult\
00255             should have been:\n$opts(-res)"
00256         } else {
00257         set res "BAD_RES"
00258         }
00259         set bench($opts(-desc)) $res
00260         puts $BENCH(OUTFID) [list Sourcing "$opts(-desc): $res"]
00261     } else {
00262         if {($opts(-ipre) != "") || ($opts(-ipost) != "")} {
00263 
00264         # We do the averaging on our own, to allow untimed
00265         # pre/post execution per iteration. We catch and
00266         # handle problems in the pre/post code as if
00267         # everything was executed as one block (like it would
00268         # be in the other path). We are using floating point
00269         # to avoid integer overflow, easily happening when
00270         # accumulating a high number (iterations) of large
00271         # integers (microseconds).
00272 
00273         set total 0.0
00274         for {set i 0} {$i < $opts(-iter)} {incr i} {
00275             set code 0
00276             if {$opts(-ipre) != ""} {
00277             set code [catch {uplevel \#0 $opts(-ipre)} res]
00278             if {$code} break
00279             }
00280             set code [catch {uplevel \#0 [list time $opts(-body) 1]} res]
00281             if {$code} break
00282             set total [expr {$total + [lindex $res 0]}]
00283             if {$opts(-ipost) != ""} {
00284             set code [catch {uplevel \#0 $opts(-ipost)} res]
00285             if {$code} break
00286             }
00287         }
00288         if {!$code} {
00289             set res [list [expr {int ($total/$opts(-iter))}] microseconds per iteration]
00290         }
00291         } else {
00292         set code [catch {uplevel \#0 \
00293             [list time $opts(-body) $opts(-iter)]} res]
00294         }
00295         if {!$BENCH(THREADS)} {
00296         if {$code == 0} {
00297             # Get just the microseconds value from the time result
00298             set res [lindex $res 0]
00299         } elseif {$code != 666} {
00300             # A 666 result code means pass it through to the bench
00301             # suite. Otherwise throw errors all the way out, unless
00302             # we specified not to throw errors (option -errors 0 to
00303             # libbench).
00304             if {$BENCH(ERRORS)} {
00305             return -code $code -errorinfo $errorInfo \
00306                 -errorcode $errorCode
00307             } else {
00308             set res "ERR"
00309             }
00310         }
00311         set bench($opts(-desc)) $res
00312         puts $BENCH(OUTFID) [list Sourcing "$opts(-desc): $res"]
00313         } else {
00314         # Threaded runs report back asynchronously
00315         thread::send $BENCH(us) \
00316             [list thread_report $opts(-desc) $code $res]
00317         }
00318     }
00319     }
00320     if {($opts(-post) != "") && [catch {uplevel \#0 $opts(-post)} err] \
00321         && $BENCH(ERRORS)} {
00322     return -code error "post code threw error:\n$err"
00323     }
00324     return
00325 }
00326 
00327 ret  usage () {
00328     set me [file tail [info script]]
00329     puts stderr "Usage: $me ?options?\
00330         \n\t-help           # print out this message\
00331         \n\t-rmatch <regexp>    # only run tests matching this pattern\
00332         \n\t-match <glob>       # only run tests matching this pattern\
00333         \n\t-interp <name>      # name of interp (tries to get it right)\
00334         \n\t-thread <num>       # number of threads to use\
00335         \n\tfileList        # files to benchmark"
00336     exit 1
00337 }
00338 
00339 /* */
00340 /*  Process args*/
00341 /* */
00342 if {[catch { BENCH = (INTERP) [info nameofexec]}]} {
00343      BENCH = (INTERP) $argv0
00344 }
00345 foreach {var val} {
00346     ERRORS      1
00347     MATCH       {}
00348     RMATCH      {}
00349     OUTFILE     stdout
00350     FILES       {}
00351     ITERS       1000
00352     THREADS     0
00353         PKGDIR          {}
00354     EXIT        "[info exists tk_version]"
00355 } {
00356     if {![info exists BENCH($var)]} {
00357      BENCH = ($var) [subst $val]
00358     }
00359 }
00360  BENCH = (EXIT) 1
00361 
00362 if {[llength $argv]} {
00363     while {[llength $argv]} {
00364      key =  [lindex $argv 0]
00365     switch -glob -- $key {
00366         -help*  { usage }
00367         -err*   {  BENCH = (ERRORS)  [lindex $argv 1] }
00368         -int*   {  BENCH = (INTERP)  [lindex $argv 1] }
00369         -rmat*  {  BENCH = (RMATCH)  [lindex $argv 1] }
00370         -mat*   {  BENCH = (MATCH)   [lindex $argv 1] }
00371         -iter*  {  BENCH = (ITERS)   [lindex $argv 1] }
00372         -thr*   {  BENCH = (THREADS) [lindex $argv 1] }
00373             -pkg*       {  BENCH = (PKGDIR)  [lindex $argv 1] }
00374         default {
00375         foreach arg $argv {
00376             if {![file exists $arg]} { usage }
00377             lappend BENCH(FILES) $arg
00378         }
00379         break
00380         }
00381     }
00382      argv =  [lreplace $argv 0 1]
00383     }
00384 }
00385 
00386 if {[string length $BENCH(PKGDIR)]} {
00387      auto = _path [linsert $auto_path 0 $BENCH(PKGDIR)]
00388 }
00389 
00390 if {$BENCH(THREADS)} {
00391     /*  We have to be able to load threads if we want to use threads, and*/
00392     /*  we don't want to create more threads than we have files.*/
00393     if {[catch {package require Thread}]} {
00394      BENCH = (THREADS) 0
00395     } elseif {[llength $BENCH(FILES)] < $BENCH(THREADS)} {
00396      BENCH = (THREADS) [llength $BENCH(FILES)]
00397     }
00398 }
00399 
00400 rename exit exit.true
00401 ret  exit args (
00402     type error ", type called \", type exit $, type args\" , type in , type benchmark , type test"
00403 )
00404 
00405 if {[string compare $BENCH(OUTFILE) stdout]} {
00406      BENCH = (OUTFID) [open $BENCH(OUTFILE) w]
00407 } else {
00408      BENCH = (OUTFID) stdout
00409 }
00410 
00411 /* */
00412 /*  Everything that gets output must be in pairwise format, because*/
00413 /*  the data will be collected in via an 'array set'.*/
00414 /* */
00415 
00416 if {$BENCH(THREADS)} {
00417     /*  Each file must run in it's own thread because of all the extra*/
00418     /*  header stuff they have.*/
00419     /* set DEBUG 1*/
00420     ret  thread_one (optional id =0) {
00421     global BENCH
00422     set file [lindex $BENCH(FILES) 0]
00423     set BENCH(FILES) [lrange $BENCH(FILES) 1 end]
00424     if {[file exists $file]} {
00425         incr BENCH(inuse)
00426         puts $BENCH(OUTFID) [list Sourcing $file]
00427         if {$id} {
00428         set them $id
00429         } else {
00430         set them [thread::create]
00431         thread::send -async $them { load {} Thread }
00432         thread::send -async $them \
00433             [list array set BENCH [array get BENCH]]
00434         thread::send -async $them \
00435             [list proc bench_tmpfile {} [info body bench_tmpfile]]
00436         thread::send -async $them \
00437             [list proc bench_rm {args} [info body bench_rm]]
00438         thread::send -async $them \
00439             [list proc bench {args} [info body bench]]
00440         }
00441         if {[info exists ::DEBUG]} {
00442         puts stderr "SEND [clock seconds] thread $them $file INUSE\
00443         $BENCH(inuse) of $BENCH(THREADS)"
00444         }
00445         thread::send -async $them [list source $file]
00446         thread::send -async $them \
00447             [list thread::send $BENCH(us) [list thread_ready $them]]
00448         #thread::send -async $them { thread::unwind }
00449     }
00450     }
00451 
00452     ret  thread_em () {
00453     global BENCH
00454     while {[llength $BENCH(FILES)]} {
00455         if {[info exists ::DEBUG]} {
00456         puts stderr "THREAD ONE [lindex $BENCH(FILES) 0]"
00457         }
00458         thread_one
00459         if {$BENCH(inuse) >= $BENCH(THREADS)} {
00460         break
00461         }
00462     }
00463     }
00464 
00465     ret  thread_ready (type id) {
00466     global BENCH
00467 
00468     incr BENCH(inuse) -1
00469     if {[llength $BENCH(FILES)]} {
00470         if {[info exists ::DEBUG]} {
00471         puts stderr "SEND ONE [clock seconds] thread $id"
00472         }
00473         thread_one $id
00474     } else {
00475         if {[info exists ::DEBUG]} {
00476         puts stderr "UNWIND thread $id"
00477         }
00478         thread::send -async $id { thread::unwind }
00479     }
00480     }
00481 
00482     ret  thread_report (type desc , type code , type res) {
00483     global BENCH bench errorInfo errorCode
00484 
00485     if {$code == 0} {
00486         # Get just the microseconds value from the time result
00487         set res [lindex $res 0]
00488     } elseif {$code != 666} {
00489         # A 666 result code means pass it through to the bench suite.
00490         # Otherwise throw errors all the way out, unless we specified
00491         # not to throw errors (option -errors 0 to libbench).
00492         if {$BENCH(ERRORS)} {
00493         return -code $code -errorinfo $errorInfo \
00494             -errorcode $errorCode
00495         } else {
00496         set res "ERR"
00497         }
00498     }
00499     set bench($desc) $res
00500     }
00501 
00502     ret  thread_finish (optional delay =4000) {
00503     global BENCH bench
00504     set val [expr {[llength [thread::names]] > 1}]
00505     #set val [expr {$BENCH(inuse)}]
00506     if {$val} {
00507         after $delay [info level 0]
00508     } else {
00509         foreach desc [array names bench] {
00510         puts $BENCH(OUTFID) [list $desc $bench($desc)]
00511         }
00512         if {$BENCH(EXIT)} {
00513         exit.true ; # needed for Tk tests
00514         }
00515     }
00516     }
00517 
00518      BENCH = (us) [thread::id]
00519      BENCH = (inuse) 0 ; /*  num threads in use*/
00520     puts $BENCH(OUTFID) [list __THREADED [package provide Thread]]
00521 
00522     thread_em
00523     thread_finish
00524     vwait forever
00525 } else {
00526     foreach BENCH(file) $BENCH(FILES) {
00527     if {[file exists $BENCH(file)]} {
00528         puts $BENCH(OUTFID) [list Sourcing $BENCH(file)]
00529         source $BENCH(file)
00530     }
00531     }
00532 
00533     foreach desc [array names bench] {
00534     puts $BENCH(OUTFID) [list $desc $bench($desc)]
00535     }
00536 
00537     if {$BENCH(EXIT)} {
00538     exit.true ; /*  needed for Tk tests*/
00539     }
00540 }
00541 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1