'''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 }}} become: {{{ 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. ) }}} become: {{{ 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 ! itot=0 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 ENDIF ENDIF GOTO 10 20 CONTINUE ! ! Allocate text cdnambuff for condensed namelist ! ALLOCATE( CHARACTER(itot) :: cdnambuff ) WRITE(*,*) 'ALLOCATED ', itot ! ! Second pass: read and transfer pruned characters into cdnambuff ! REWIND(iun) itot=1 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 ELSE inl = inl - 1 ENDIF 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 ENDIF ENDIF GOTO 30 40 CONTINUE WRITE(*,*) 'ASSIGNED ',itot - 1 ! ! Close namelist file ! CLOSE(iun) !write(*,'(32A)') cdnambuff END SUBROUTINE load_nml }}}