Version 1 (modified by acc, 12 months ago) (diff)

Options for improving namelist file access

This page outlines some ideas for reducing the sequential disk access to namelist files at start-up. The current method, where every process repeatedly reads and rewinds a few small text files, is only as scalable as the file-system allows and is unlikely to be fit for purpose when moving to higher core counts.

A simple option, that requires very little code change, is to replace the current unit number variables with character arrays that contain the whole of the corresponding namelist file. I.e. use F90 internal files so that disk access is performed only once in a single sequential read by each process.

If necessary, disk access could be reduced further by having only one process read each namelist and broadcast the array to all others. I believe this is now feasible since Seb's recent updates remove any namelist access before MPI initialisation. This should be considered at a future stage.

Stage 1

  • Replace namelist unit variables with allocatable character arrays, e.g. in in_out_manger.F90:

   INTEGER ::   numnam_ref      =   -1      !: logical unit for reference namelist
   INTEGER ::   numnam_cfg      =   -1      !: logical unit for configuration specific namelist


   CHARACTER(:), ALLOCATABLE :: numnam_ref    !: character buffer for reference namelist
   CHARACTER(:), ALLOCATABLE :: numnam_cfg    !: character buffer for configuration namelist
  • Replace ctl_opn statements with calls to a new routine that will fill the buffer, e.g.: in nemogcm.F90:
                  CALL ctl_opn( numnam_ref,        'namelist_ref',     'OLD', 'FORMATTED', 'SEQUENTIAL', -1, -1, .FALSE. )
                  CALL ctl_opn( numnam_cfg,        'namelist_cfg',     'OLD', 'FORMATTED', 'SEQUENTIAL', -1, -1, .FALSE. )


                  CALL load_nml( numnam_ref,       'namelist_ref',                                           -1, .FALSE. )
                  CALL load_nml( numnam_cfg,       'namelist_cfg',                                           -1, .FALSE. )
  • Remove all REWIND statements involving the old unit variables (REWIND is not permitted (or necessary) on internal files)
  • Remove any CLOSE statements involving the old unit numbers. the load_nml routine will have closed the namelist file after reading.

Job done.

There are however a few caveats:

  • To be memory efficient, the character arrays should be allocated with the minimum length required to accommodate the namelist information. This requires each file to be read twice: once to determine and sum up the trimmed length of each line (comments can be stripped at this stage); and once to fill the newly allocated buffer. The example load_nml routine shown below achieves this. As an indication of requirements, this routine compacts the namelist_ref file into about 33KB. Further savings could be made by reducing multiple spaces to single spaces but the need to preserve any character strings within the namelists would complicate the code and would only save around 20%.
  • Reads from internal files always start at the beginning of the array (although substrings of the array can be provided to alter the starting point). This means the current practise in BDY of having multiple, identically named namelist blocks depending on the number of boundary segments will not work with internal files. Scanning the array to determine starting points for each block is possible but messy because of the need to support FORTRAN's case-independent attitude. A simpler solution will be to insist on appending a count to second and subsequent occurrences of the same block.
  • To be considered: will this work with AGRIF? load_nml still uses ctl_opn to open the namelist file so the correct file will be opened but will each nest get independent copies of the character arrays? What does Agrif_Get_Unit currently do?

The prototype load_nml routine (added to lib_mpp.F90)

   SUBROUTINE load_nml( cdnambuff , cdnamfile, kout, ldwp)
      CHARACTER(:)    , ALLOCATABLE, INTENT(INOUT) :: cdnambuff
      CHARACTER(LEN=*), INTENT(IN )                :: cdnamfile
      CHARACTER(LEN=256)                           :: chline
      INTEGER, INTENT(IN)                          :: kout
      LOGICAL, INTENT(IN)                          :: ldwp
      INTEGER                                      :: itot, iun, iltc, inl, ios
      ! Check if the namelist buffer has already been allocated. Return if it has.
      IF ( ALLOCATED( cdnambuff ) ) RETURN
      ! Open namelist file
      CALL ctl_opn( iun, cdnamfile, 'OLD', 'FORMATTED', 'SEQUENTIAL', -1, kout, ldwp )
      ! First pass: count characters excluding comments and trimable white space
  10  READ(iun,'(A256)',END=20,ERR=20) chline
      iltc = LEN_TRIM(chline)
      IF ( iltc.GT.0 ) THEN
       inl = INDEX(chline, '!')
       IF( inl.eq.0 ) THEN
        itot = itot + iltc + 1                             ! +1 for the newline character
       ELSEIF( inl.GT.0 .AND. LEN_TRIM( chline(1:inl-1) ).GT.0 ) THEN
        itot = itot + inl                                  !  includes +1 for the newline character
      GOTO 10
      ! Allocate text cdnambuff for condensed namelist
      ALLOCATE( CHARACTER(itot) :: cdnambuff )
      WRITE(*,*) 'ALLOCATED ', itot
      ! Second pass: read and transfer pruned characters into cdnambuff
  30  READ(iun,'(A256)',END=40,ERR=40) chline
      iltc = LEN_TRIM(chline)
      IF ( iltc.GT.0 ) THEN
       inl = INDEX(chline, '!')
       IF( inl.eq.0 ) THEN
        inl = iltc
        inl = inl - 1
       IF( inl.GT.0 .AND. LEN_TRIM( chline(1:inl) ).GT.0 ) THEN
          cdnambuff(itot:itot+inl-1) = chline(1:inl)
          WRITE( cdnambuff(itot+inl:itot+inl), '(a)' ) NEW_LINE('A')
          itot = itot + inl + 1
      GOTO 30
      WRITE(*,*) 'ASSIGNED ',itot - 1
      ! Close namelist file
      !write(*,'(32A)') cdnambuff