README.org 16.6 KB
Newer Older
Daniel Kochmański's avatar
Daniel Kochmański committed
1
* The Metering System
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

The Metering System is a portable Common Lisp code profiling tool.
It gathers timing and consing statistics for specified functions 
while a program is running.

The Metering System is a combination of 
  -  the Monitor package written by Chris McConnell
  -  the Profile package written by Skef Wholey and Rob MacLachlan

The two systems were merged and extended by Mark Kantrowitz.

Metering System was refreshed and adapted to the 2016 realities by
Daniel Kochmański.

This code is in the public domain and is distributed without warranty
of any kind. 

Bug reports, comments, and suggestions should be sent to
daniel@turtleware.eu.


** Change Log
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| Date      | Name | Ver | Notes                                                                                                                                                                                                          |
|-----------+------+-----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 26-JUN-90 | mk   |     | Merged functionality of Monitor and Profile packages.                                                                                                                                                          |
| 26-JUN-90 | mk   |     | Now handles both inclusive and exclusive statistics with respect to nested calls. (Allows it to subtract total monitoring overhead for each function, not just the time spent monitoring the function itself.) |
| 26-JUN-90 | mk   |     | The table is now saved so that one may manipulate the data (sorting it, etc.) even after the original source of the data has been cleared.                                                                     |
| 25-SEP-90 | mk   |     | Added get-cons functions for Lucid 3.0, MACL 1.3.2 required-arguments functions for Lucid 3.0, Franz Allegro CL, and MACL 1.3.2.                                                                               |
| 25-JAN-91 | mk   |     | Now uses fdefinition if available.                                                                                                                                                                             |
| 25-JAN-91 | mk   |     | Replaced (and :allegro (not :coral)) with :excl.  Much better solution for the fact that both call themselves :allegro.                                                                                        |
| 5-JUL-91  | mk   |     | Fixed warning to occur only when file is loaded uncompiled.                                                                                                                                                    |
| 5-JUL-91  | mk   |     | When many unmonitored functions, print out number instead of whole list.                                                                                                                                       |
| 24-MAR-92 | mk   |     | Updated for CLtL2 compatibility. space measuring doesn't work in MCL, but fixed so that timing statistics do.                                                                                                  |
| 26-MAR-92 | mk   |     | Updated for Lispworks. Replaced :ccl with (and :ccl (not :lispworks)).                                                                                                                                         |
| 27-MAR-92 | mk   |     | Added get-cons for Allegro-V4.0.                                                                                                                                                                               |
| 01-JAN-93 | mk   | 2.0 | Support for MCL 2.0, CMU CL 16d, Allegro V3.1/4.0/4.1, Lucid 4.0, ibcl                                                                                                                                         |
| 25-JAN-94 | mk   | 2.1 | Patches for CLISP from Bruno Haible.                                                                                                                                                                           |
| 26-FEB-16 | dk   | 3.0 | General clean-up - removal of the obsolete implementations support and constructs, testing on the active implementations etc.                                                                                  |
40
| 26-FEB-16 | dk   | 3.1 | Make warnings more ASDF-friendly                                                                                                                                                                               |
41
| 25-MAY-16 | dk   | 3.2 | Improve the documentation (document ignore-no-calls parameter)                                                                                                                                                 |
42 43 44 45 46 47 48 49 50 51 52


** To Do

   - Speed up monitoring code. Replace use of hash tables with an embedded
     offset in an array so that it will be faster than using gethash. 
     (i.e., svref/closure reference is usually faster than gethash).
   - Check robustness with respect to profiled functions.
   - Add option to record caller statistics -- this would list who
     called which functions and how often.
   - Switches to turn timing/CONSING statistics collection on/off.
Daniel Kochmański's avatar
Daniel Kochmański committed
53
   - Additional tests.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

** Notes

   METERING has been tested (successfully) in the following lisps:

   | Name  |    Version | Testing date | Addional notes            |
   |-------+------------+--------------+---------------------------|
   | ABCL  |      1.3.3 | Feb 2016     |                           |
   | CCL   |       1.11 | Feb 2016     |                           |
   | CMUCL |        21a | Feb 2016     |                           |
   | CLISP |       2.49 | Feb 2016     |                           |
   | ECL   |     16.1.2 | Feb 2016     |                           |
   | GCL   | 2.6.13-pre | Feb 2016     |                           |
   | SBCL  |      1.3.2 | Feb 2016     |                           |
   | XCL   |  0.0.0.291 | Feb 2016     | unstable (random crashes) |


   METERING needs to be tested in the following lisps:
     - Allegro
     - LispWorks
     - mocl

Daniel Kochmański's avatar
Daniel Kochmański committed
76
   On XCL and GCL loading system with ASDF won't work.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

   GCL has to first load the file, then compile it and load
   the compiled before:
   #+BEGIN_SRC lisp
     (load "metering.cl") ; GCL's issue
     (compile-file "metering.cl")
     (load *)
   #+END_SRC

   METERING may be load with ASDF or by compiling and
   loading file:
   #+BEGIN_SRC lisp
     #-asdf
     (load (compile-file "metering.cl"))

     ,#+asdf
     (load "metering.asd")
     (asdf:load-system 'metering)
   #+END_SRC

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251

** Documentation

This system runs in any valid Common Lisp. Two small
implementation-dependent changes can be made to improve performance
and prettiness. In the section labelled "Implementation Dependent
Changes" below, you should tailor the functions REQUIRED-ARGUMENTS,
GET-CONS to your implementation for the best results. If GET-CONS is
not specified for your implementation, no consing information will be
reported. If you tailor these functions for a particular version of
Common Lisp, we'd appreciate receiving the code.



*** Usage Notes

SUGGESTED USAGE:

Start by monitoring big pieces of the program, then carefully choose
which functions close to, but not in, the inner loop are to be 
monitored next. Don't monitor functions that are called by other
monitored functions: you will only confuse yourself.

If the per-call time reported is less than 1/10th of a second, then
consider the clock resolution and profiling overhead before you believe
the time. It may be that you will need to run your program many times
in order to average out to a higher resolution.

The easiest way to use this package is to load it and execute either
#+BEGIN_SRC lisp
  (mon:with-monitoring (names*) ()
    your-forms*)
#+END_SRC
or                      
#+BEGIN_SRC lisp
  (mon:monitor-form your-form)
#+END_SRC

The former allows you to specify which functions will be monitored; the
latter monitors all functions in the current package. Both automatically
produce a table of statistics. Other variants can be constructed from
the monitoring primitives, which are described below, along with a
fuller description of these two macros.

For best results, compile this file before using.


CLOCK RESOLUTION:

Unless you are very lucky, the length of your machine's clock "tick" is
probably much longer than the time it takes a simple function to run.
For example, on the IBM RT, the clock resolution is 1/50th of a second.
This means that if a function is only called a few times, then only the
first couple of decimal places are really meaningful.


MONITORING OVERHEAD:

The added monitoring code takes time to run every time that the monitored
function is called, which can disrupt the attempt to collect timing 
information. In order to avoid serious inflation of the times for functions
that take little time to run, an estimate of the overhead due to monitoring
is subtracted from the times reported for each function. 

Although this correction works fairly well, it is not totally accurate,
resulting in times that become increasingly meaningless for functions
with short runtimes. For example, subtracting the estimated overhead
may result in negative times for some functions. This is only a concern
when the estimated profiling overhead is many times larger than 
reported total CPU time.

If you monitor functions that are called by monitored functions, in
:inclusive mode the monitoring overhead for the inner function is
subtracted from the CPU time for the outer function. [We do this by
counting for each function not only the number of calls to *this*
function, but also the number of monitored calls while it was running.]
In :exclusive mode this is not necessary, since we subtract the
monitoring time of inner functions, overhead & all.

Otherwise, the estimated monitoring overhead is not represented in the
reported total CPU time. The sum of total CPU time and the estimated
monitoring overhead should be close to the total CPU time for the
entire monitoring run (as determined by TIME).

A timing overhead factor is computed at load time. This will be incorrect
if the monitoring code is run in a different environment than this file
was loaded in. For example, saving a core image on a high performance
machine and running it on a low performance one will result in the use
of an erroneously small overhead factor.


If your times vary widely, possible causes are:
   - Garbage collection.  Try turning it off, then running your code.
     Be warned that monitoring code will probably cons when it does
     (get-internal-run-time).



*** Interface
#+BEGIN_EXAMPLE
WITH-MONITORING (&rest functions)                         [Macro]
                (&optional (nested :exclusive) 
                           (threshold 0.01)
                           (key :percent-time))
                &body body
The named functions will be set up for monitoring, the body forms executed,
a table of results printed, and the functions unmonitored. The nested,
threshold, and key arguments are passed to report-monitoring below.

MONITOR-FORM form                                         [Macro]
              &optional (nested :exclusive)
                        (threshold 0.01)
                        (key :percent-time)
All functions in the current package are set up for monitoring while
the form is executed, and automatically unmonitored after a table of
results has been printed. The nested, threshold, and key arguments 
are passed to report-monitoring below.

*MONITORED-FUNCTIONS*                                     [Variable]
This holds a list of all functions that are currently being monitored.

MONITOR &rest names                                       [Macro]
The named functions will be set up for monitoring by augmenting
their function definitions with code that gathers statistical information
about code performance. As with the TRACE macro, the function names are
not evaluated. Calls the function MON::MONITORING-ENCAPSULATE on each
function name. If no names are specified, returns a list of all 
monitored functions.

If name is not a symbol, it is evaled to return the appropriate
closure. This allows you to monitor closures stored anywhere like
in a variable, array or structure. Most other monitoring packages 
can't handle this. 

MONITOR-ALL &optional (package *package*)                 [Function]
Monitors all functions in the specified package, which defaults to
the current package.

UNMONITOR &rest names                                     [Macro]
Removes monitoring code from the named functions. If no names are
specified, all currently monitored functions are unmonitored.

RESET-MONITORING-INFO name                                [Function]
Resets the monitoring statistics for the specified function.

RESET-ALL-MONITORING                                      [Function]
Resets the monitoring statistics for all monitored functions.

MONITORED name                                            [Function]
Predicate to test whether a function is monitored.

REPORT-MONITORING &optional names                         [Function]
                            (nested :exclusive) 
                            (threshold 0.01)
                            (key :percent-time)
252
                            ignore-no-calls
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
Creates a table of monitoring information for the specified list
of names, and displays the table using display-monitoring-results.
If names is :all or nil, uses all currently monitored functions.
Takes the following arguments:
   - NESTED specifies whether nested calls of monitored functions
     are included in the times for monitored functions.
     o  If :inclusive, the per-function information is for the entire
        duration of the monitored function, including any calls to
        other monitored functions. If functions A and B are monitored,
        and A calls B, then the accumulated time and consing for A will
        include the time and consing of B.  Note: if a function calls
        itself recursively, the time spent in the inner call(s) may
        be counted several times.
     o  If :exclusive, the information excludes time attributed to
        calls to other monitored functions. This is the default.
   - THRESHOLD specifies that only functions which have been executed
     more than threshold percent of the time will be reported. Defaults
     to 1%. If a threshold of 0 is specified, all functions are listed,
     even those with 0 or negative running times (see note on overhead).
   - KEY specifies that the table be sorted by one of the following
     sort keys:
        :function       alphabetically by function name
        :percent-time   by percent of total execution time
        :percent-cons   by percent of total consing
        :calls          by number of times the function was called
        :time-per-call  by average execution time per function
        :cons-per-call  by average consing per function
        :time           same as :percent-time
        :cons           same as :percent-cons
282 283
   - IGNORE-NO-CALLS specifies whenever functions which were not
     called should be reported.
284 285 286

DISPLAY-MONITORING-RESULTS &optional (threshold 0.01)     [Function]
                                     (key :percent-time)
287
                                     (ignore-no-calls t)
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
Prints a table showing for each named function:
   - the total CPU time used in that function for all calls
   - the total number of bytes consed in that function for all calls
   - the total number of calls
   - the average amount of CPU time per call
   - the average amount of consing per call
   - the percent of total execution time spent executing that function
   - the percent of total consing spent consing in that function
Summary totals of the CPU time, consing, and calls columns are printed.
An estimate of the monitoring overhead is also printed. May be run
even after unmonitoring all the functions, to play with the data.

SAMPLE TABLE:

                                                 Cons
                   %     %                       Per      Total   Total
  Function         Time  Cons  Calls  Sec/Call   Call     Time    Cons
  ----------------------------------------------------------------------
  FIND-ROLE:       0.58  0.00    136  0.003521      0  0.478863       0
  GROUP-ROLE:      0.35  0.00    365  0.000802      0  0.292760       0
  GROUP-PROJECTOR: 0.05  0.00    102  0.000408      0  0.041648       0
  FEATURE-P:       0.02  0.00    570  0.000028      0  0.015680       0
  ----------------------------------------------------------------------
  TOTAL:                        1173                   0.828950       0
  Estimated total monitoring overhead: 0.88 seconds
#+END_EXAMPLE