MAINPM User's Guide, Chapter 5

previous   next   top   contents   index   framed top   this page unframed


5. PC Sampling

The MAINPM command PC is available on some systems and causes MAINPM to monitor the program with PC (program counter) sampling. The PC command has various options; the full syntax is:

PC {VIRTUAL | REAL} {t} {WHEN callPattern ;}

Only an unambiguous prefix of each keyword needs to be given; case is ignored.

The PC command must be given before the monitored program is executed.

In each form of the PC command, MAINPM causes an interrupt to occur every t microseconds (or as close to t microseconds as the operating system's timer permits). At each such interrupt MAINSAIL examines the PC to determine which PROCEDURE is currently being executed, and increments its PC count by 1. At the end of program execution the total PC counts for each PROCEDURE are printed out (omitting PROCEDUREs with no sampled PCs). If the program runs sufficiently long with respect to the number of PC interrupts, the result is a profile of where the program is spending its time, since the PC count for each PROCEDURE is approximately proportional to the amount of time spent in that PROCEDURE. Sampled PCs not in any MAINSAIL PROCEDURE (e.g., PCs in the MAINSAIL bootstrap or foreign or operating system code) are credited in the listing to foreignPcs.

If t is omitted, 10000 is used by default; i.e., a PC interrupt occurs every 10000 microseconds, or 100 times per second. The actual frequency of PC interrupts is system-dependent, and the value of t is really just a hint to MAINPM; many systems may be able to interrupt far less frequently than every microsecond.

This timing option requires no compiler subcommands. The user program is slowed down, but not very much (in particular, the slowdown is much less than that caused by the MODTIME or PROCTIME compiler subcommands).

5.1. PC VIRTUAL {t}

PC VIRTUAL {t} monitors PCs with the time interval t applied to “virtual” time; i.e., the timer interrupts occur every t microseconds of “virtual” time. The exact definition of virtual time is system-dependent, but it is roughly the time spent executing the process (CPU time, plus possibly some operating system overhead on behalf of the process), as opposed to real or wall clock time. In particular, where possible, it does not include time spent waiting on I/O or other external events since during such time the operating system is probably executing some other process.

5.2. PC REAL {t}

PC REAL {t} monitors PCs with the time interval t applied to “real” time; i.e., the timer interrupts occur every t microseconds of “real” time. Real time is meant to be “wall clock time”, i.e., time as measured by a stopwatch. Typically the foreignPcs listing is higher for this option, since any time spent waiting on I/O is attributed to foreignPcs. For example, if a program prompts for user input, and the user waits a minute before responding, then one minute's worth of PC sampling is credited to foreignPcs (since the code that waits for terminal input is in the MAINSAIL bootstrap, which is considered a foreign PC).

This form of monitoring can help you see how much time your program spends waiting for external events such as I/O, though since all foreign PCs are summarized in one foreignPcs value, you cannot distinguish PCs sampled for different types of I/O.

5.3. PC {t}

PC {t} is the same as PC VIRTUAL {t} if virtual PC monitoring is available; otherwise, the same as PC REAL {t} if real PC monitoring is available; otherwise, an error message is given.

A given system may support one, both, or neither of the PC monitoring concepts. An error message is given if an unsupported PC monitoring concept is chosen.

5.4. PC WHEN callPattern

The WHEN clause, if present, indicates which PROCEDUREs or MODULEs must be active in order for timer interrupts to be counted. If no WHEN clause is present, all timer interrupts are counted.

The WHEN clause may extend across several lines; thus, its last line must be terminated with a semicolon.

The syntax for call patterns below uses curly braces for grouping elements, vertical bars for alternatives, and asterisk to mean zero or more of the preceding element, except as noted:

callPattern = callThread { | callThread }*
(Note: the vertical bar above is part of the syntax; i.e., if a call pattern consists of more than one call thread, the call threads are separated by vertical bars.)
callThread = primary { -> primary }*
primary = ( callPattern ) | * | [:] moduleName [ . procedureName ] [:]
(Note: the asterisk above is part of the syntax, and denotes an indefinite number of PROCEDUREs or MODULEs in the call stack.)

A call pattern consists of one or more call threads, any one of which must match the state of the program when a timer interrupt occurs in order for the interrupt to be counted.

A call thread consists of one or more PROCEDURE or MODULE names, separated by arrows (->). The asterisk can be thought of as matching any sequence of PROCEDUREs or MODULEs. A call thread matches the state of the program if all PROCEDUREs and MODULEs specified in the call thread are active, and were called in the same order as they were specified in the call thread. Furthermore, if two consecutive PROCEDURE or MODULE names in a call thread are not separated by an asterisk, then the first PROCEDURE or MODULE must have directly called the second one. That is, it does not count if the first PROCEDURE or MODULE called some other PROCEDURE which in turn called the second PROCEDURE or MODULE. Finally, if the last PROCEDURE or MODULE name in a call thread is not followed by an asterisk, that PROCEDURE or MODULE must be the currently executing PROCEDURE or MODULE when the timer interrupt occurs.

The colons shown in the syntax for primary, if present, affect only how timer interrupts are counted, not which interrupts are counted. A colon immediately after a PROCEDURE or MODULE name causes that PROCEDURE to be treated as the PROCEDURE in which the timer interrupt occurred, even if it was not the current PROCEDURE. That is, both the shallow and deep PC count for that PROCEDURE are incremented, and the deep PC counts for its callers would be incremented. The counts for the PROCEDUREs that were called by the PROCEDURE whose name is followed by the colon are not incremented.

A colon immediately before a PROCEDURE or MODULE name causes that PROCEDURE or MODULE's caller to be treated as the PROCEDURE in which the timer interrupt occurred; i.e., the immediate caller's shallow and deep PC count would be incremented, as well as the deep PC counts for that caller's callers.

If no colons are specified, the current PROCEDURE's shallow PC count is incremented.

If more than one colon is specified in a call pattern, the one that takes effect is the first one encountered in the first sequence of call threads that matches the state of the program when the timer interrupt occurs.

A concrete example may make the motivation for the PC command's WHEN clause easier to understand.

Imagine that you have a program suffering from poor performance. Unbeknownst to you, the reason is that your program is calling newUpperBound unnecessarily often. However, when you issue the PC command without a WHEN clause, all you notice is that the program is spending a lot of time in the $ARYMOD PROCEDURE $findArrayDscr. This PROCEDURE is a part of the MAINSAIL runtime system, and you do not call it directly, so this information does not immediately help you figure out the cause of your own program's problems.

You need to figure out where $findArrayDscr is called from. Using a WHEN clause to MAINPM's PC command, you could determine which of $findArrayDscr's callers accounts for most of the time by specifying the following PC command:

PC WHEN :$arymod.$findarraydscr;

and then running the program. The only timer interrupts that would be counted would be those that occurred while $findArrayDscr was the current PROCEDURE. Specifying the colon before the PROCEDURE name would cause the shallow PC counts of $findArrayDscr's various callers to be incremented, not that of $findArrayDscr itself. Since the only timer interrupts being counted would be those that occur in $findArrayDscr, it would not be very informative to credit those interrupts to $findArrayDscr's own shallow PC count.

In this example, MAINPM would report that all calls to $findArrayDscr were made by the $ARYMOD PROCEDURE newArrayN (another part of the runtime system). This information is not much more useful than the information available from the PC command without the WHEN clause. At this point, you could proceed in two ways. You could run the program again after specifying the following MAINPM command:

PC WHEN :$arymod.newarrayn->$arymod.$findarraydscr;

This time, MAINPM would report that almost all calls to newArrayN were made by the $ARYMOD PROCEDURE doNewUpperBound, which tells you that your program is calling newUpperBound a lot.

Alternatively, you could have noticed from the first run's report file that the deep PC count for doNewUpperBound was almost as high as the total number of timer interrupts that were counted. From this you might be able to infer that most of the time spent in $findArrayDscr was because of doNewUpperBound.

Call patterns can be much more elaborate than this. For example, the pattern:

(m.a->m.b->* | m.c) -> (*->m.d | m.e->*)

means that a timer interrupt should be counted only if the program state at the time of the interrupt matches either of the call threads *->m.d or m.e->* and, deeper in the call stack, also matches either of the threads m.a->m.b->* or m.c. In other words, one of the following call threads would have to match the program state at the time of the interrupt:

m.a->m.b->*->m.d
m.a->m.b->*->m.e->*
m.c->*->m.d
m.c->m.e->*

5.5. Deep PC Monitoring

When doing PC monitoring, MAINPM causes an interrupt to occur at regular intervals throughout a program's execution. With deep PC monitoring, MAINSAIL maintains two counts for every active MAINSAIL PROCEDURE: the number of interrupts at which the PROCEDURE was the currently executing PROCEDURE (the PROCEDURE's shallow PC count), and the number of interrupts at which the PROCEDURE was active but not necessarily the current PROCEDURE (the PROCEDURE's deep PC count). A PROCEDURE is considered to be active if it has an activation in the current coroutine and the activation has not been suspended by the raising of an exception; when a PROCEDURE is active but not the current PROCEDURE, the PROCEDURE is in the middle of a call to some other PROCEDURE.

So, for example, if a PROCEDURE A called a PROCEDURE B and a timer interrupt occurred during B's execution, then B's shallow and deep PC counts would both be incremented, and A's deep PC count would be incremented but its shallow count would not be.

When the MAINPM LIST command is given after executing a monitored program, MAINPM lists each PROCEDURE's shallow and deep PC counts in tabular form. Each count is also expressed as a percentage of the total number of interrupts that occurred during the program's execution. The higher the total number of interrupts, the more likely it is that each reported percentage approximates the actual percentage of time (shallow or deep) spent in the PROCEDURE. The results are listed twice, with the PROCEDUREs sorted first by their shallow PC counts (in decreasing order) and then by deep PC counts (also in decreasing order).

Occasionally, when a timer interrupt occurs, MAINSAIL's memory is in an inconsistent state because the runtime system is in the middle of a memory management operation. In such cases, the timer interrupt is charged to a so-called “critical section” of code instead of to any particular PROCEDURE. Time spent in critical sections is displayed in the summary of statistics at the end of MAINPM's report file.

There are times during a PROCEDURE's prologue or epilogue when MAINSAIL cannot walk the PROCEDURE stack normally. If a timer interrupt occurs during one of these times, the caller's deep PC count is not incremented, but all other deep PC counts (including the current PROCEDURE's) are incremented. Fixing this bug would not be impossible, but is probably not worth the effort. Anyone who uses deep PC monitoring should be aware that the numbers contained in the report may be slightly inconsistent with each other; however, these inconsistencies are not usually serious.

5.6. Restrictions on PC Monitoring

PC monitoring is not necessarily available on every operating system; check the current MAINSAIL release note or your operating-system-specific documentation for details. On some systems, shallow PC monitoring may be available, but deep PC monitoring may be unavailable.

PC monitoring may be disabled during directory manipulations, such as creating, moving, deleting, or renaming directories. This is due to the inability of the child process that is created on some operating systems to perform these tasks to handle the PC interrupt. If your program does much directory manipulation, your PC monitoring results may be inaccurate.


previous   next   top   contents   index   framed top   this page unframed

MAINPM User's Guide, Chapter 5