previous next top contents index framed top this page unframed
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).
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.
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.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).
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.
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->*
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.
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.
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.