The timer_tree_type
module implements a lightweight method for creating
and maintaining a nested tree of timers. The tree is automatically generated
via the nested starting and stopping of named timers. Because timers are
distinguished by name and position in the tree, a start/stop pair will give
rise to different timers when the code containing it is executed within
different timer nestings. As a result, the timing of shared code can be easily
and automatically partitioned according to its use.
The most basic use of the timers requires no more than calling start_timer
and stop_timer
in matching pairs with an arbitrary name as in this example:
use timer_tree_type
call start_timer ('A')
call start_timer ('B')
call stop_timer ('B')
call start_timer ('C')
call start_timer ('B')
call stop_timer ('B')
call stop_timer ('C')
call stop_timer ('A')
call start_timer ('B')
call start_timer ('X')
call stop_timer ('X')
call start_timer ('Y')
call stop_timer ('Y')
call start_timer ('Z')
call stop_timer ('Z')
call stop_timer ('B')
call start_timer ('A')
call stop_timer ('A')
call write_timer_tree(unit=6, indent=2)
The only restriction is that the timers be nested: the timer most recently
started (and still running) is the only one eligible to be stopped. Starting A,
then B, and then stopping A is not allowed, for example. Any incorrect nesting
of timers will be detected by stop_timer
at run time. The calls, of course,
can be in different program units.
The same name may be used for multiple start/stop pairs, and start/stop pairs with the same name and nesting are regarded as a single timer and their elapsed time accumulated accordingly. Otherwise they are regarded as distinct timers. An example of the latter is timer B. It is started and stopped at three different positions within the call nesting, and this is reflected in the associated timer tree pictured here:
digraph FLATLAND { 0 -> 1 -> 2; 1 -> 3 -> 4; 0 -> 5 -> 6; 5 -> 7; 5 -> 8; 0 [label="root"] 1 [label=A] 2 [label=B] 3 [label=C] 4 [label=B] 5 [label=B] 6 [label=X] 7 [label=Y] 8 [label=Z] }Each pair is regarded as a distinct timer. Timer A, on the other hand, is an example of the former case. It is started and stopped twice, but at the same position within the call nesting, and thus appears in the timer tree only once. The elapsed time for each call pair is accumulated in the single timer as expected.
Finally, a call to write_timer_tree
(at any time) will write the
time accumulated thus far for each timer, using indentation to express the
nested structure of the timer tree. The output would look something like
this for the code example:
A: 4.90000E-02
B: 1.00000E-02
C: 1.90000E-02
B: 9.00000E-03
B: 2.80000E-02
X: 9.00000E-03
Y: 1.00000E-02
Z: 9.00000E-03
Because timers are distinguished by name and position in the tree, a start/stop pair will give rise to different timers when the code containing it is executed within different timer nestings. As a result, the timing of a portion of shared code can be easily and automatically partitioned according to its use. This is a distinctive feature of this implementation.
For portability the Fortran intrinsic subroutine system_clock
is used to
get the wall clock time, and the resolution of the timers is thus limited
by the resolution of this subroutine, which can vary significantly between
systems.
For most cases this covers everything one needs to know to in order to use
the global timer tree managed by the timer_tree_type
module. The next
section describes some additional functionality that may be useful, and in
section that follows it, the timer_tree
derived type that underlies the
implementation is described.
The following subroutines operate on the global timer tree.
call start_timer(name [,handle])
Start the timer with the specified character string name
that is a child
of the current timer. If no such child exists, one is created with this name.
This child timer then becomes the current timer. If the optional integer
argument handle
is specified, it returns a handle to the timer which can
be used as an argument to write_timer_tree
or read_timer
.
call stop_timer(name [,stat [,errmsg]])
Stop the current timer. The current timer’s parent becomes the new current
timer. It is an error if the current timer does not have the specified name.
If the optional integer argument stat
is present, it is assigned the
value 0 if no error was encountered; otherwise it is assigned a non-zero
value. In the latter case, the allocatable deferred-length character string
errmsg
, if present, is assigned an explanatory message. If stat
is
not present and an error occurs, the error message is written to the
preconnected error unit and the program exits with a nonzero status.
call write_timer_tree(unit, indent [,handle])
Write the accumulated time for each timer to the specified logical unit
,
using indentation to express the nested structure of the timer tree. The
incremental number of spaces to indent for successive tree levels is given
by indent
. If an optional integer handle
returned by start_timer
is specified, only the accumulated times for that timer and its decendents
are written.
call read_timer(handle, time)
Returns in the default real argument time
, the elapsed time for the timer
corresponding to the handle
returned by start_timer
. The timer may
be running or stopped.
call reset_timer_tree
resets the timer tree to its initial, empty state.
Sometimes more direct access to the timer tree data is needed than is provided
by either read_timer
or write_timer_tree
. For example, the data may
need to be communicated between processes in a parallel simulation, or it may
need to be written in a format that can be easily read and used to initialize
the timer tree. The following subroutines provide such functionality.
call serialize_timer_tree(tree, name, time)
Get the current state of the timer tree in flat arrays. Timers may be
running or stopped and their state is unaltered. The deferred-length,
allocatable character array name
and allocatable default real array
time
return the timer names and elapsed times indexed by tree node
number. The allocatable default integer array tree
returns the structure
of the tree as a sequence of node numbers: node numbers appear in matching
pairs, like opening and closing parentheses, with the intervening sequence
describing the trees of its children, recusively. The nodes are numbered so
that the initial node of the pairs appear in sequential order. This enables
a simple reconstruction of the tree. All three allocatable array arguments
are allocated by the subroutine; they are reallocated if necessary.
call deserialize_timer_tree(tree, name, time)
Define the state of the timer tree using the tree
, name
, and
time
arrays returned by serialize_timer_tree
. This can be used to
initialize the timer tree with results from a previous simulation, for
example. Note that timer handles are not preserved.
This code fragment shows how to use the output of serialize_timer_tree
to
reconstruct the tree by writing a group of nested XML tags that reproduce the
structure of the tree. The integer m
records the high-water mark of the
tree node numbers encountered, which, due of the way the nodes are numbered,
can be used to distinguish the initial and final node of each pair.
m = 0 ! the high-water mark of node indices encountered
do j = 1, size(tree)
n = tree(j)
if (n > m) then ! first encounter of this node index
print *, '<TIMER NAME="', trim(name(n)), '" TIME="', time(n), '">'
m = n
else
print *, '</TIMER>'
end if
end do
The timer tree used thus far is in fact a type(timer_tree)
object that is
a private module variable in the timer_tree_type
module (a ‘singleton’).
The procedures described above all operate on this single global instance. But
applications can also declare and use their own type(timer_tree)
variables.
This derived type has the following type bound subroutines which have the same interface and effect as those described above, except that they operate on the specific instance rather than the global timer tree.
start(name [,handle])
Same interface as start_timer
.
stop(name [,stat [,errmsg]])
Same interface as stop_timer
.
write(unit, indent [,handle])
Same interface as write_timer_tree
.
read(handle, time)
Same interface as read_timer
.
serialize(tree, name, time)
Same interface as serialize_timer_tree
.
deserialize(tree, name, time)
Same interface as deserialize_timer_tree
.
Note that objects of this derived type:
are properly finalized when the object ceases to exist.
should not be used in assignment statements; only the default intrinsic assignment is available, and its semantics are unlikely to be what is wanted.