next up previous index
Next: Restoring and Saving the Up: Timing a Job Previous: Timing a Job in

Timing a Job in Fortran

Even Fortran-90 does not provide portable means for checking CPU time usage without calling vendor and operating system specific routines.

Fortran 90 defines two intrinsic procedures date_and_time and system_clock, which return elapsed wall-clock time in various formats.

The date_and_time procedure takes 4 arguments, all of which are optional:

date: intent(out)
a character string at least 8 characters long
time: intent(out)
a character string at least 10 characters long
zone: intent(out)
a character string at least 5 characters long
values: intent(out)
an array of integers at least 8 entries long

For our purposes we don't need date or time returned as strings. We only need the numbers, which are returned in values, so we'll call this procedure using a keyword argument list:

call date_and_time (values=time_array)
where time_array is our array of integers. The returned values will have the following ordering:
time_array(1)
year
time_array(2)
month of the year
time_array(3)
day of the month
time_array(4)
time offset with respect to UTC in minutes
time_array(5)
hour of the day
time_array(6)
minutes of the hour
time_array(7)
seconds of the minute
time_array(8)
milliseconds of the second

Subroutine system_clock is somewhat easier to use. It takes 3 optional arguments:

count, intent(out)
an integer
count_rate: intent(out)
an integer
count_max: intent(out)
an integer

This function is somewhat similar to C-function clock, in the sense that it counts time at a rate of count_rate counts per second up to count_max, and then resets itself to zero and resumes the counting. But unlike clock this function measures wall-clock time, not the CPU time. As you will see from the following example, under AIX procedure system_clock resets every day at midnight. But this particular behaviour is not specified in F90 standard.

In fact there is no intrinsic Fortran-90 procedure for measuring CPU time. For that we have to use XL-Fortran service and utility function etime_. At this stage the program ceases to be portable, so it is a good idea to isolate the parts of the code that rely on etime_ with cpp #ifdef .. #endif brackets. In the example below I use gcc -E -P -C instead of cpp. It is important to remove cpp generated line references before passing the file to Fortran compiler. Option -P ensures that. The importance of option -C will become clearer in our next Fortran-90 example.

Function etime_ is defined in the xlfutility module, which must be included with the use statement:

use xlfutility
The function takes a structure of type tb_type as argument (intent(out)) and returns the sum of system and user components of the CPU time since the start of the execution of a process. Additionally user time and system time are written on usrtime and systime slots of the argument.

For more information about service and utility procedures provided in xlfutility read the ``XL Fortran for AIX, Language Reference, Version 3, Release 2'' manual, pages 445-451. The manual, in compressed PostScript, can be found in the /usr/lpp/xlf/ps directory on any SP node.

The following Fortran 90 program shows how to use all three procedures in order to time your computation.

      program f_time

#ifdef XLF
      use xlfutility

! Variables for function dtime_

      real(4) elapsed_0, elapsed_1
      type (tb_type) etime_struct_0, etime_struct_1
#endif

! Variables for subroutine system_clock

      integer count_0, count_1, count_rate, count_max

! Variables for subroutine date_and_time

      integer time_array_0(8), time_array_1(8)
      real start_time, finish_time

! Variables for computation

      integer n
      parameter (n = 1000000)
      double precision a(n), b(n), c(n)

      write (6, '(1x, 1a)') 'using F90 procedure date_and_time ...'
      write (6, '(1x, 1a)') 'using F90 procedure system_clock ...'
#ifdef XLF
      write (6, '(1x, 1a)') 'using XLF function dtime_ ...'
#endif
      
! Mark the beginning of the program

      call date_and_time(values=time_array_0)
      start_time = time_array_0 (5) * 3600 + time_array_0 (6) * 60 &
           + time_array_0 (7) + 0.001 * time_array_0 (8)
      call system_clock(count_0, count_rate, count_max)
#ifdef XLF
      elapsed_0 = etime_(etime_struct_0)
#endif

      write (6, '(8x, 1a, 1f16.6)') 'begin (date_and_time):  ', &
           start_time
      write (6, '(8x, 1a, 1f16.6)') 'begin (system_clock):   ', &
           count_0 * 1.0 / count_rate
#ifdef XLF
      write (6, '(8x, 1a, 1f16.6)') 'begin (etime_%usrtime): ', &
           etime_struct_0%usrtime
      write (6, '(8x, 1a, 1f16.6)') 'begin (etime_%systime): ', &
           etime_struct_0%systime
#endif

! Sleep for 5 seconds

#ifdef XLF
      write (6, '(16x, 1a)') 'sleep for 5 seconds ... '
      call sleep_ (5)
#endif

! Perform some computation

      write (6, '(16x, 1a)') 'perform some computation ... '
      a = (/ (i, i = 1, n) /)
      a = sqrt(a)
      b = 1.0 / a
      c = b - a      

! Mark the end of the program

      call date_and_time(values=time_array_1)
      finish_time = time_array_1 (5) * 3600 + time_array_1 (6) * 60 &
           + time_array_1 (7) + 0.001 * time_array_1 (8)
      call system_clock(count_1, count_rate, count_max)
#ifdef XLF
      elapsed_1 = etime_(etime_struct_1)
#endif

      write (6, '(8x, 1a, 1f16.6)') 'end (date_and_time):    ', &
           finish_time
      write (6, '(8x, 1a, 1f16.6)') 'end (system_clock):     ', &
           count_1 * 1.0 / count_rate
#ifdef XLF
      write (6, '(8x, 1a, 1f16.6)') 'end (etime_%usrtime):   ', &
           etime_struct_1%usrtime
      write (6, '(8x, 1a, 1f16.6)') 'end (etime_%systime):   ', &
           etime_struct_1%systime
#endif

! Print elapsed time

      write (6, '(8x, 1a, 1f16.6)') 'elapsed wall clock time:', &
           finish_time - start_time           
#ifdef XLF
      write (6, '(8x, 1a, 1f16.6)') 'elapsed CPU time:       ', &
           etime_struct_1%usrtime - etime_struct_0%usrtime
#endif

      end program f_time

This file must be passed through cpp first in order to generate the plain Fortran code. Then the code must be compiled and linked with Fortran-90 compiler. The most convenient way to go about all that is to write appropriate instructions on a Makefile and use make to generate the binary. Here is the Makefile that you can use for this F90 example code:

F90     = xlf90
CPP     = gcc -E -P -C
DEFINES = -DXLF
OPTS    = # -g

all: f_time

f_time: f_time.o
        $(F90) $(OPTS) -o f_time f_time.o

f_time.o: f_time.f
        $(F90) $(OPTS) -c f_time.f

f_time.f: f_time.cpp
        $(CPP) $(DEFINES) f_time.cpp > f_time.f

clean: 
        rm -f f_time.f f_time.o f_time
On the IU SP system I have compiled and run this program as follows:
<6:05:00 !705 $ gcc -E -P -C -DXLF f_time.cpp > f_time.f
gustav@sp19:../LoadLeveler 16:05:15 !706 $ xlf90 -o f_time f_time.f
** f_time   === End of Compilation 1 ===
1501-510  Compilation successful for file f_time.f.
gustav@sp19:../LoadLeveler 16:05:26 !707 $ time -p ./f_time
 using F90 procedure date_and_time ...
 using F90 procedure system_clock ...
 using XLF function dtime_ ...
        begin (date_and_time):      57937.957031
        begin (system_clock):       57937.949219
        begin (etime_%usrtime):         0.000000
        begin (etime_%systime):         0.010000
                sleep for 5 seconds ... 
                perform some computation ... 
        end (date_and_time):        57944.312500
        end (system_clock):         57944.308594
        end (etime_%usrtime):           0.850000
        end (etime_%systime):           0.210000
        elapsed wall clock time:        6.355469
        elapsed CPU time:               0.850000
real 6.43
user 0.85
sys 0.21
gustav@sp19:../LoadLeveler 16:05:44 !708 $
And, as before, we can see that our internal estimates agree pretty well with results returned by UNIX program time. There is a small discrepancy of 0.08 s in the estimate of the elapsed wall-clock time (6.35s versus 6.43s), the explanation of which is left to the reader as an exercise. Also observe that time returned by procedure system_clock is roughly the same as time returned by procedure date_and_time, which means that system_clock must be reset at midnight, as I have already remarked above.


next up previous index
Next: Restoring and Saving the Up: Timing a Job Previous: Timing a Job in
Zdzislaw Meglicki
2001-02-26