DISPATCH
io_mod.f90
1 !===============================================================================
2 !> $Id: 2dbf9184a65cf7224399bde7bb8ef20adde87879 $
3 !===============================================================================
4 MODULE io_mod
5  USE omp_mod
6  USE omp_timer_mod
7  USE mpi_mod
8  USE io_unit_mod
9  USE timer_mod
10  USE os_mod
11  implicit none
12  private
13  logical:: mpi_trace=.false.
14  type io_t
15  integer:: verbose=0, input, output, data_unit
16  logical:: master=.true.
17  logical:: do_legacy, do_direct, omp_trace
18  character(len=16):: method='legacy'
19  integer:: levelmax
20  integer:: word_size=4
21  integer:: iodir=-1
22  integer:: format=0
23  integer:: nml_version=0
24  integer:: restart=-1
25  integer:: nwrite=0, ntask=0, ntotal=0
26  integer:: dims(3)=0, mpi_dims(3), mpi_odims(3)=1
27  character(len=64):: inputname, outputname, datadir='data', top
28  character(len=64):: rundir='data', inputdir='data'
29  character(len=1):: sep = '/'
30  character(len=72):: hl='-------------------------------------------------------------------------'
31  character(len=72):: hs='*************************************************************************'
32  ! flag values
33  logical:: do_trace=.false.
34  logical:: do_output=.true.
35  logical:: do_debug=.false.
36  logical:: do_flags=.false.
37  logical:: do_stop=.false.
38  logical:: guard_zones=.false.
39  logical:: namelist_errors=.true.
40  logical:: needs_check=.false.
41  logical:: halt=.false. ! used in data_io and amr_io
42  integer:: task_logging=0
43  integer:: log_sent=0
44  integer:: time_derivs=0
45  integer:: id_debug=0
46  integer:: id_track=0
47  integer:: nv=0
48  integer:: id=1 ! the task to print a one-liner for
49  real(8):: end_time=1d30
50  real(8):: out_time=1d0
51  real(8):: out_next=1d0
52  real(8):: print_time=0d0
53  real(8):: print_next=0d0
54  real(8):: dtime=huge(1d0) ! the shortest time step
55  real(8):: gb=0.0
56  real(8):: job_seconds=1d30
57  real(8):: processing=0d0
58  real:: flag_time=1.
59  real:: flag_max=10.
60  real:: ampl=0.0
61  real:: grace=0.0
62  real:: smallr=0.0
63  real:: courant=0.0
64  real:: dmin=1.0
65  real:: dmax=1.0
66  real:: gb_out=0.0
67  contains
68  procedure:: init
69  procedure:: debug
70  procedure:: check_flags
71  procedure:: bits_mem
72  procedure:: gb_mem
73  procedure:: gb_print
74  procedure:: namelist_warning
75  procedure, nopass:: abort
76  procedure, nopass:: assert
77  procedure, nopass:: header
78  procedure, nopass:: print_hl
79  end type
80  character(len=64), save:: inputname, outputname
81  type(io_t), public:: io
82  public mch, io_unit, stderr, stdout, stdin
83 CONTAINS
84 
85 !===============================================================================
86 !> Function suitable for testing in debug print statements
87 !===============================================================================
88 LOGICAL FUNCTION debug (self, verbose)
89  class(io_t):: self
90  integer:: verbose
91  debug = self%master .and. (self%verbose >= verbose)
92 END FUNCTION debug
93 
94 !===============================================================================
95 !> Initialize I/O related parameters. The first thread that enters here blocks
96 !> access in a critical region, reads namelist parameters, and stores them in a
97 !> global, public instance, which we normally refer to. Other calls pick up the
98 !> global values and store them in other instances (which may be the same).
99 !===============================================================================
100 SUBROUTINE init (self, name)
101  class(io_t):: self
102  character(len=120), save:: id = &
103  'io_mod.f90 $Id: 2dbf9184a65cf7224399bde7bb8ef20adde87879 $'
104  integer, save:: verbose=0, levelmax=10
105  character(len=*), optional:: name
106  character(len=64):: filename, datadir='data', inputdir='data'
107  character(len=64), save:: top='../../'
108  character(len=8), save:: method='legacy'
109  integer, save:: id_debug=-1, restart=-9, format=0, time_derivs=0, nml_version=1
110  integer, save:: log_sent=0, task_logging=0
111  logical, save:: first_time=.true., omp_trace=.false., do_validate=.false.
112  logical, save:: do_debug=.false., do_trace=.false., do_output=.false., exist, &
113  do_legacy=.false., do_flags=.false., do_direct=.false., guard_zones=.false.
114  logical ,save:: namelist_errors=.true.
115  namelist /io_params/ verbose, do_debug, do_trace, do_output, do_flags, &
116  do_validate, do_legacy, do_direct, levelmax, omp_trace, id_debug, top, &
117  datadir, inputdir, method, restart, format, guard_zones, time_derivs, log_sent, &
118  task_logging, namelist_errors, nml_version
119  real:: test
120  integer:: iostat
121  character(len=120):: ids = &
122  '$Id: 2dbf9184a65cf7224399bde7bb8ef20adde87879 $ io/io_mod.f90'
123  !.............................................................................
124  if (mpi%master) then
125  print'(a)', self%hl
126  print'(a)', trim(ids)
127  end if
128  self%master = mpi%master
129  io%master = mpi%master
130  !$omp parallel
131  !$omp end parallel
132  !-----------------------------------------------------------------------------
133  ! I/O unit numbers, for convenience and backwards compatibility. All other
134  ! unit numbers should be taken directly from io_unit_mod
135  !-----------------------------------------------------------------------------
136  self%input = io_unit%input
137  !-----------------------------------------------------------------------------
138  ! Use serial region to avoid threads using the input file simultaneously
139  !-----------------------------------------------------------------------------
140  if (first_time) then
141  !---------------------------------------------------------------------------
142  ! The input filename is input.nml, or as given on the command line
143  !---------------------------------------------------------------------------
144  call getarg(1,filename) ! command line
145  if (filename/=' '.and.trim(filename)/='input.nml') then
146  self%inputname = filename ! run_name.nml
147  self%rundir = trim(datadir)//self%sep// & ! data/run_name/
148  trim(filename(1:index(filename,'.')-1))//self%sep
149  else
150  self%inputname = 'input.nml'
151  self%rundir = trim(datadir)//self%sep ! data/
152  end if
153  call os%mkdir (trim(self%rundir))
154  self%outputname = self%rundir ! compatibility
155  write (filename,'(a,i5.5,"/")') trim(self%outputname), 0
156  call os%mkdir (trim(filename))
157  !---------------------------------------------------------------------------
158  open (io_unit%nml, file=trim(self%rundir)//'params.nml', form='formatted', status='unknown')
159  open (io_unit%hash, file=trim(self%rundir)//'hash.log', form='formatted', status='unknown')
160  open (self%input, file=self%inputname, form='formatted', status='old')
161  !---------------------------------------------------------------------------
162  ! Read io_params namelist
163  !---------------------------------------------------------------------------
164  inputdir = self%rundir
165  rewind(io_unit%input)
166  read (io_unit%input, io_params, iostat=iostat)
167  if (iostat > 0) call self%namelist_warning ('io_params')
168  if (mpi%master .and. .not. do_validate) then
169  print'(a,i4)', ' n_socket =', omp%nsockets
170  print'(a,i4)', ' n_core =', omp%ncores
171  print'(a,i4)', ' n_thread =', omp_nthreads
172  end if
173  self%task_logging = task_logging
174  self%format = format
175  self%nml_version = nml_version
176  self%inputdir = inputdir
177  call ensure_dirname (self%inputdir)
178  call ensure_dirname (self%outputname)
179  !---------------------------------------------------------------------------
180  ! OMP specific setup; need to set io_unit in all threadprivate instances
181  !---------------------------------------------------------------------------
182  !$omp parallel
183  !$omp critical (open_cr)
184  if (omp%master) then
185  io_unit%verbose = verbose
186  else
187  io_unit%verbose = -2
188  end if
189  io_unit%rundir = self%rundir
190  io_unit%inputdir = self%inputdir
191  io_unit%outputname = self%outputname
192  io_unit%do_validate = do_validate
193  !---------------------------------------------------------------------------
194  ! Log-file names:
195  ! -- io_unit%output : stdout on rank 0, same as io_unit%mpi on rank 1-n
196  ! -- io_unit%mpi : data/run/rank_rrrrr.log
197  ! -- io_unit%queue : data/run/rank_rrrrr.nq
198  ! -- io_unit%dispatcher : data/run/rank_rrrrr.disp
199  ! -- io_unit%log : data/run/thread_rrrrr_ttt.log
200  !---------------------------------------------------------------------------
201  call open_rank_file (io_unit%queue , ".nq" , 'formatted')
202  call open_rank_file (io_unit%mpi , ".log" , 'formatted')
203  call open_rank_file (io_unit%task , ".task" , 'formatted')
204  call open_rank_file (io_unit%dbg , ".dbg" , 'unformatted')
205  call open_rank_file (io_unit%dump , ".dump" , 'unformatted')
206  call open_rank_file (io_unit%validate , ".val" , 'unformatted')
207  call open_rank_file (io_unit%dispatcher, ".disp" , 'formatted')
208  !---------------------------------------------------------------------------
209  ! Open MPI rank and OMP thread-specific log files if omp_trace is true,
210  ! else fall back to io_unit%log being just MPI rank-specific
211  !---------------------------------------------------------------------------
212  if (omp_trace .and. omp%nthreads>1) then
213  io_unit%log = 110 + omp%mythread()
214  call open_rank_file (io_unit%log , ".log" , 'formatted', omp%mythread())
215  else
216  io_unit%log = io_unit%mpi
217  end if
218  !---------------------------------------------------------------------------
219  ! For non-master MPI ranks, redirect log file output to rundir/rank_rrrrr.log
220  !---------------------------------------------------------------------------
221  if (mpi%rank > 0) then
222  io_unit%output = io_unit%mpi
223  end if
224  !$omp end critical (open_cr)
225  io_unit%master = io%master .and. omp%master
226  write (io_unit%log,'(a,2i4,3l4)') &
227  'io_mod::init io_unit%log, omp%thread, io_unit%master, io_unit%verbose:', &
228  io_unit%log, omp%thread, io_unit%master, io_unit%verbose
229  !$omp end parallel
230  !---------------------------------------------------------------------------
231  ! stdout is the same as the the %output unit, and is not threadprivate
232  !---------------------------------------------------------------------------
233  stdout = io_unit%output
234  write (stdout,io_params)
235  !---------------------------------------------------------------------------
236  ! Backwards compatibility
237  !---------------------------------------------------------------------------
238  io%output = io_unit%output
239  io%data_unit = io_unit%data
240  write(io%output,*) 'logfile:', filename
241  write(io%output,*) '======================================================================='
242  write(io%output,*) 'NOTE: Reading parameters from '//trim(self%inputname)
243  write(io%output,*) ' This version was compiled with default real KIND=', kind(test)
244  write(io%output,*) '======================================================================='
245  first_time=.false.
246  end if
247  self%guard_zones = guard_zones
248  self%time_derivs = time_derivs
249  self%restart = restart
250  self%datadir = datadir
251  self%id_debug = id_debug
252  self%verbose = verbose
253  self%do_debug = do_debug
254  self%do_trace = do_trace
255  self%do_output = do_output
256  self%do_flags = do_flags
257  self%do_legacy = do_legacy
258  self%do_direct = do_direct
259  self%omp_trace = omp_trace
260  self%log_sent = log_sent
261  self%levelmax = levelmax
262  self%inputname = inputname
263  self%top = top
264  self%method = method
265  self%namelist_errors = namelist_errors
266  if (do_legacy) self%method = 'legacy'
267  if (do_direct) self%method = 'direct'
268  io_unit%top = top
269  io_unit%do_validate = do_validate
270  stdout = io_unit%output
271  call timer%init
272 contains
273  !-----------------------------------------------------------------------------
274  subroutine ensure_dirname (s)
275  character(len=*):: s
276  integer:: l
277  l = len(trim(s))
278  if (s(l:l) /= '/') s(l+1:l+1)='/'
279  end subroutine
280  subroutine open_rank_file (unit, ext, form, thread)
281  integer:: unit
282  integer, optional:: thread
283  character(len=*):: ext, form
284  character(len=64):: filename
285  !-----------------------------------------------------------------------------
286  if (present(thread)) then
287  open (unit=unit, file=trim(filename), form=form, status='unknown')
288  write (io_unit%threadbase,'(a,"thread_",i5.5,"_",i3.3)') &
289  trim(self%outputname), mpi%rank, thread
290  filename = trim(io_unit%threadbase)//ext
291  else
292  write (io_unit%rankbase,'(a,"rank_",i5.5)') &
293  trim(self%outputname), mpi%rank
294  filename = trim(io_unit%rankbase)//ext
295  end if
296  open (unit=unit, file=trim(filename), form=form, status='unknown')
297  end subroutine
298 END SUBROUTINE init
299 
300 !===============================================================================
301 !> Register increase of memory, counted in storage_size bits, times a count
302 !===============================================================================
303 SUBROUTINE bits_mem (self, bits, count, label)
304  class(io_t) :: self
305  integer:: bits, count
306  character(len=*), optional:: label
307  !-----------------------------------------------------------------------------
308  if (io%verbose>2) then
309  if (present(label)) then
310  write(io%output,1) bits,' bits per word', count,' words, for', &
311  (bits/(8.*1024.**3))*count, ' GB '//trim(label)
312  1 format(i3,a,i8,a,f6.3,a)
313  else
314  write(io%output,1) bits,' bits per word', count,' words, for', &
315  (bits/(8.*1024.**3))*count, ' GB'
316  end if
317  end if
318  call self%gb_mem ((bits/(8.*1024.**3))*count)
319 END SUBROUTINE bits_mem
320 
321 !===============================================================================
322 !> Register increase of memory, counted in gigabytes
323 !===============================================================================
324 SUBROUTINE gb_mem (self, gb)
325  class(io_t):: self
326  real:: gb, lgb
327  real, save:: gb_next=1.0
328  !-----------------------------------------------------------------------------
329  if (mpi%master) then
330  !$omp atomic capture
331  self%gb = self%gb + gb
332  lgb = self%gb
333  !$omp end atomic
334  if (abs(lgb - gb_next) > 1.0) then
335  !$omp critical (gb_cr)
336  if (abs(lgb - gb_next) > 1.0) then
337  do while (abs(lgb - gb_next) > 1.0)
338  call self%gb_print(lgb)
339  gb_next = gb_next + sign(1.0,lgb - gb_next)
340  end do
341  end if
342  !$omp end critical (gb_cr)
343  end if
344  end if
345 END SUBROUTINE gb_mem
346 
347 !===============================================================================
348 !> Register increase of memory, counted in gigabytes
349 !===============================================================================
350 SUBROUTINE gb_print (self, gb)
351  class(io_t):: self
352  real :: gb
353  !.............................................................................
354  print '(1x,a,f8.3,a)', 'process memory allocated:', gb,' GB'
355 END SUBROUTINE gb_print
356 
357 
358 !===============================================================================
359 !> Check debug flag files. To avoid having to synchronize when using MPI, we
360 !> give the ranks 5 seconds to detect and read flag files, and then we let one
361 !> rank remove them.
362 !===============================================================================
363 SUBROUTINE check_flags (self)
364  class(io_t) :: self
365  real(8), save :: last_checked=0d0
366  real(8) :: flag_time
367  logical :: exists
368  character(len=80) :: file
369  integer, save :: itimer=0
370  !.............................................................................
371  !$omp master
372  call timer%begin ('io_t%check_flags', itimer)
373  flag_time = merge(self%flag_time, self%flag_max, self%do_flags)
374  !------------------------------------------------------------------------------
375  ! If ready to detect flag files, try.
376  !------------------------------------------------------------------------------
377  if (io%processing==0d0) then
378  if (wallclock() > last_checked+flag_time) then
379  last_checked = wallclock()
380  call check_all (.false.)
381  end if
382  !-----------------------------------------------------------------------------
383  ! After processing for 10 sec, all ranks turn off processing
384  !-----------------------------------------------------------------------------
385  else if (wallclock() > io%processing+10d0) then
386  io%processing = 0d0
387  !-----------------------------------------------------------------------------
388  ! After 5 sec, the master thread on the master rank removes the flag files
389  !-----------------------------------------------------------------------------
390  else if (wallclock() > io%processing+5d0) then
391  if (io%master) call check_all (.true.)
392  end if
393  call timer%end (itimer)
394  !$omp end master
395 contains
396 
397  !-----------------------------------------------------------------------------
398  ! Thee OMP master checks for all valid types of flag files
399  !-----------------------------------------------------------------------------
400  subroutine check_all (remove)
401  logical:: remove
402  !.............................................................................
403  !$omp critical (flag_cr)
404  file = trim(io%outputname)//'flag'
405  inquire (file=trim(file), exist=exists)
406  if (exists) then
407  !---------------------------------------------------------------------------
408  ! If a flag file is detected, start the processing
409  !---------------------------------------------------------------------------
410  io%processing = last_checked + flag_time
411  open (io_unit%flag, file=trim(file), form='formatted', status='old')
412  if (remove) then
413  close (io_unit%flag, status='delete')
414  else
415  close (io_unit%flag)
416  end if
417  ! logical
418  call check ('do_trace.flag' , remove, lvalue=self%do_trace)
419  call check ('do_flags.flag' , remove, lvalue=self%do_flags)
420  call check ('do_debug.flag' , remove, lvalue=self%do_debug)
421  call check ('do_output.flag' , remove, lvalue=self%do_output)
422  call check ('stop.flag' , remove, lvalue=self%do_stop)
423  ! integer
424  call check ('id_debug.flag' , remove, ivalue=self%id_debug)
425  call check ('id_track.flag' , remove, ivalue=self%id_track)
426  call check ('verbose.flag' , remove, ivalue=self%verbose)
427  ! single prec
428  call check ('flag_time.flag' , remove, rvalue=self%flag_time)
429  call check ('flag_max.flag' , remove, rvalue=self%flag_max)
430  call check ('ampl.flag' , remove, rvalue=self%ampl)
431  call check ('grace.flag ' , remove, rvalue=self%grace)
432  call check ('smallr.flag ' , remove, rvalue=self%smallr)
433  call check ('courant.flag ' , remove, rvalue=self%courant)
434  ! double prec
435  call check ('out_time.flag' , remove, dvalue=self%out_time)
436  call check ('out_next.flag' , remove, dvalue=self%out_next)
437  call check ('print_time.flag' , remove, dvalue=self%print_time)
438  call check ('end_time.flag' , remove, dvalue=self%end_time)
439  call check ('sec_per_report.flag' , remove, dvalue=timer%sec_per_report)
440  !-------------------------------------------------------------------------
441  if (io%do_stop .and. mpi%master) then
442  write (stdout,*) file
443  open (io_unit%flag, file=file, form='formatted', status='unknown')
444  close (io_unit%flag, status='delete')
445  call io%abort ('stop.flag detected')
446  end if
447  end if
448  !$omp end critical (flag_cr)
449  end subroutine
450 
451  !=============================================================================
452  subroutine check (file, remove, lvalue, ivalue, rvalue, dvalue)
453  character(len=*) :: file
454  logical :: remove
455  logical, optional :: lvalue
456  integer, optional :: ivalue
457  real , optional :: rvalue
458  real(8), optional :: dvalue
459  logical :: exists
460  integer :: iostat
461  character(len=80) :: f
462  !.............................................................................
463  f = trim(io%outputname)//trim(file)
464  if (remove) then
465  inquire (file=trim(f), exist=exists)
466  if (exists) then
467  open (io_unit%flag, file=trim(f), form='formatted', status='old', iostat=iostat)
468  close (io_unit%flag, status='delete')
469  end if
470  else
471  inquire (file=trim(f), exist=exists)
472  if (exists) then
473  open (io_unit%flag, file=trim(f), form='formatted', status='old', iostat=iostat)
474  if (present(ivalue)) read (io_unit%flag,*) ivalue
475  if (present(rvalue)) read (io_unit%flag,*) rvalue
476  if (present(dvalue)) read (io_unit%flag,*) dvalue
477  if (present(lvalue).and.iostat==0) read (io_unit%flag,*,iostat=iostat) lvalue
478  if (iostat/=0) lvalue = .not.lvalue
479  close (io_unit%flag)
480  if (present(lvalue)) write (io_unit%output,*) 'flag: ', file(1:index(file,'.')-1), ' =', lvalue
481  if (present(ivalue)) write (io_unit%output,*) 'flag: ', file(1:index(file,'.')-1), ' =', ivalue
482  if (present(rvalue)) write (io_unit%output,*) 'flag: ', file(1:index(file,'.')-1), ' =', rvalue
483  if (present(dvalue)) write (io_unit%output,*) 'flag: ', file(1:index(file,'.')-1), ' =', dvalue
484  end if
485  end if
486  end subroutine
487 END SUBROUTINE check_flags
488 
489 !===============================================================================
490 !> Issue message for missing namelist in input file
491 !===============================================================================
492 SUBROUTINE namelist_warning (self, namelist, error)
493  class(io_t):: self
494  character(len=*):: namelist
495  logical, optional:: error
496  !.............................................................................
497  if (io%master) then
498  write(stdout,'(a)') ''
499  write(stdout,'(a)') '*************************************************************************************'
500  write(stdout,'(a)') '*************************************************************************************'
501  if (self%namelist_errors) then
502  write(stdout,'(a)') ' ERROR: namelist '//trim(namelist)//' had read error'
503  else
504  write(stdout,'(a)') ' WARNING: namelist '//trim(namelist)//' had read error'
505  end if
506  write(stdout,'(a)') '*************************************************************************************'
507  write(stdout,'(a)') '*************************************************************************************'
508  if (self%namelist_errors) then
509  if (present(error)) then
510  if (error) &
511  call mpi%abort('Namelist error')
512  else
513  call mpi%abort('Namelist error')
514  end if
515  end if
516  end if
517 END SUBROUTINE namelist_warning
518 
519 !===============================================================================
520 !> Interface to mpi%abort, for modules that do not otherwise USE mpi_mod
521 !===============================================================================
522 SUBROUTINE abort (error)
523  character(len=*), optional:: error
524  !.............................................................................
525  call mpi%abort (error)
526 END SUBROUTINE abort
527 
528 !===============================================================================
529 !> Interface to mpi%abort, for modules that do not otherwise USE mpi_mod
530 !===============================================================================
531 SUBROUTINE assert (ok, message)
532  logical:: ok
533  character(len=*):: message
534  !.............................................................................
535  if (.not.ok) &
536  call mpi%abort (message)
537 END SUBROUTINE assert
538 
539 !===============================================================================
540 !> Print a centered message, preceded and followed by full === lines, unless the
541 !> first character is a '-', in which case print a singe ---- message ---- line
542 !===============================================================================
543 SUBROUTINE header (str, left)
544  character(len=*):: str
545  integer, optional:: left
546  !.............................................................................
547  call timer%print()
548  if (io%master) then
549  if (str(1:1) == '-') then
550  call line (str, left)
551  else
552  call line ('=', left)
553  call line (str, left)
554  call line ('=', left)
555  end if
556  end if
557 !===============================================================================
558 !> Print a centered or left adjusted message, surrounded by repeated c chars
559 !===============================================================================
560 contains
561  subroutine line (s, left)
562  character(len=*):: s
563  integer, optional:: left
564  !...........................................................................
565  character(len=1):: c
566  integer, parameter:: w=80
567  character(len=w):: buf
568  integer:: i, j, l1, l2, l
569  !---------------------------------------------------------------------------
570  l = len_trim(s)
571  !---------------------------------------------------------------------------
572  ! Optionally, use a fixed left start, else center
573  !---------------------------------------------------------------------------
574  if (s(1:1)=='-') then
575  c = '-'
576  j = 2
577  else
578  c = '='
579  j = 1
580  end if
581  if (present(left)) then
582  l1 = left
583  else
584  l1 = (w-l)/2-1
585  end if
586  l2 = l1 + l
587  do i=1,w
588  if (l <= 1) then
589  ! --- no message ---
590  buf(i:i) = c
591  else if (i==l1 .or. i==l2) then
592  ! --- space before and after message ---
593  buf(i:i) = ' '
594  else if (i > l1 .and. i < l2 .and. j <= l) then
595  ! --- copy message ---
596  buf(i:i) = s(j:j)
597  j = j+1
598  else
599  ! --- surrounding char ---
600  buf(i:i) = c
601  end if
602  end do
603  print '(a)', buf
604  end subroutine line
605 END SUBROUTINE header
606 
607 !===============================================================================
608 SUBROUTINE print_hl
609  character(len=120), save:: hl= &
610  '--------------------------------------------------------------------------------'
611  !..............................................................................
612  if (io%master) then
613  write (io_unit%output,'(a)') trim(hl)
614  end if
615 END SUBROUTINE print_hl
616 
617 END MODULE io_mod
Each thread uses a private timer data type, with arrays for start time and total time for each regist...
Definition: timer_mod.f90:11
Support tic/toc timing, as in MATLAB, and accurate wallclock() function. The timing is generally much...
Definition: io_mod.f90:4