!*********************************************************************** !* GNU Lesser General Public License !* !* This file is part of the GFDL Flexible Modeling System (FMS). !* !* FMS is free software: you can redistribute it and/or modify it under !* the terms of the GNU Lesser General Public License as published by !* the Free Software Foundation, either version 3 of the License, or (at !* your option) any later version. !* !* FMS is distributed in the hope that it will be useful, but WITHOUT !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License !* for more details. !* !* You should have received a copy of the GNU Lesser General Public !* License along with FMS. If not, see . !*********************************************************************** ! module within MPP for handling PSETs: ! PSET: Persistent Shared-memory Execution Thread ! ! AUTHOR: V. Balaji (v.balaji@noaa.gov) ! DATE: 2006-01-15 #ifdef test_mpp_pset !PSET_DEBUG is always turned on in the test program #define PSET_DEBUG #endif module mpp_pset_mod #include use mpp_mod, only: mpp_pe, mpp_npes, mpp_root_pe, mpp_send, mpp_recv, & mpp_sync, mpp_error, FATAL, WARNING, stdout, stderr, mpp_chksum, & mpp_declare_pelist, mpp_get_current_pelist, mpp_set_current_pelist, & mpp_init, COMM_TAG_1, COMM_TAG_2, COMM_TAG_3, mpp_sync_self implicit none private !private variables integer :: pe integer :: commID !MPI communicator, copy here from pset logical :: verbose=.FALSE. logical :: module_is_initialized=.FALSE. character(len=256) :: text #ifdef use_SGI_GSM #include integer :: pSync(SHMEM_BARRIER_SYNC_SIZE) pointer( p_pSync, pSync ) !used by SHPALLOC #endif !generic interfaces interface mpp_pset_broadcast_ptr module procedure mpp_pset_broadcast_ptr_scalar module procedure mpp_pset_broadcast_ptr_array end interface interface mpp_send_ptr module procedure mpp_send_ptr_scalar module procedure mpp_send_ptr_array end interface interface mpp_recv_ptr module procedure mpp_recv_ptr_scalar module procedure mpp_recv_ptr_array end interface interface mpp_pset_print_chksum module procedure mpp_pset_print_chksum_1D module procedure mpp_pset_print_chksum_2D module procedure mpp_pset_print_chksum_3D module procedure mpp_pset_print_chksum_4D end interface !public type type :: mpp_pset_type private #ifdef IBM_FIX sequence #endif integer :: npset !number of PSETs integer :: next_in_pset, prev_in_pset !next and prev PE in PSET (cyclic) integer :: root_in_pset !PE designated to be the root within PSET logical :: root !true if you are the root PSET integer :: pos !position of current PE within pset !stack is allocated by root !it is then mapped to mpp_pset_stack by mpp_pset_broadcast_ptr real, _ALLOCATABLE :: stack(:) _NULL integer, _ALLOCATABLE :: pelist(:) _NULL !base PElist integer, _ALLOCATABLE :: root_pelist(:) _NULL !a PElist of all the roots integer, _ALLOCATABLE :: pset(:) _NULL !PSET IDs integer(POINTER_KIND) :: p_stack integer :: lstack, maxstack, hiWM !current stack length, max, hiWM integer :: commID character(len=32) :: name logical :: initialized=.FALSE. end type mpp_pset_type !public types public :: mpp_pset_type !public variables !public member functions public :: mpp_pset_create, mpp_pset_sync, mpp_pset_broadcast, & mpp_pset_broadcast_ptr, mpp_pset_check_ptr, mpp_pset_segment_array, & mpp_pset_stack_push, mpp_pset_stack_reset, mpp_pset_print_chksum, & mpp_pset_delete, mpp_pset_root, mpp_pset_numroots, mpp_pset_init, & mpp_pset_get_root_pelist, mpp_pset_print_stack_chksum contains subroutine mpp_pset_init #ifdef use_SGI_GSM integer :: err #ifdef sgi_mipspro character(len=8) :: value !won't be read integer :: lenname, lenval!won't be read #endif if( module_is_initialized )return !this part needs to be called _all_ PEs call SHMEM_BARRIER_ALL() call SHPALLOC( p_pSync, SHMEM_BARRIER_SYNC_SIZE, err, -1 ) call SHMEM_BARRIER_ALL() #ifdef sgi_mipspro call PXFGETENV( 'SMA_GLOBAL_ALLOC', 0, value, lenval, err ) if( err.NE.0 )call mpp_error( FATAL, & 'The environment variable SMA_GLOBAL_ALLOC must be set on Irix.' ) #endif #endif module_is_initialized = .TRUE. end subroutine mpp_pset_init subroutine mpp_pset_create(npset,pset,stacksize,pelist, commID) !create PSETs ! called by all PEs in parent pelist ! mpset must be exact divisor of npes integer, intent(in) :: npset !number of PSETs per set type(mpp_pset_type), intent(inout) :: pset integer, intent(in), optional :: stacksize integer, intent(in), optional :: pelist(:) integer, intent(in), optional :: commID integer :: npes, my_commID integer :: i, j, k, out_unit, errunit integer, allocatable :: my_pelist(:), root_pelist(:) call mpp_init() call mpp_pset_init() #ifdef PSET_DEBUG verbose=.TRUE. #endif out_unit = stdout() errunit = stderr() pe = mpp_pe() if(present(pelist)) then npes = size(pelist(:)) else npes = mpp_npes() endif if( mod(npes,npset).NE.0 )then write( text,'(a,2i6)' ) & 'MPP_PSET_CREATE: PSET size (npset) must divide npes exactly:'// & ' npset, npes=', npset, npes call mpp_error( FATAL, text ) end if !configure out root_pelist allocate(my_pelist(0:npes-1) ) allocate(root_pelist(0:npes/npset-1) ) if(present(pelist)) then if(.not. present(commID)) call mpp_error(FATAL, & 'MPP_PSET_CREATE: when pelist is present, commID should also be present') my_pelist = pelist my_commID = commID else call mpp_get_current_pelist(my_pelist, commID = my_commID) endif do i = 0,npes/npset-1 root_pelist(i) = my_pelist(npset*i) enddo write( out_unit,'(a,i6)' )'MPP_PSET_CREATE creating PSETs... npset=', npset if(ANY(my_pelist == pe) ) then if( pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_CREATE: PSET already initialized!' ) pset%npset = npset allocate( pset%pelist(0:npes-1) ) allocate( pset%root_pelist(0:npes/npset-1) ) pset%commID = my_commID pset%pelist = my_pelist !create the root PElist pset%root_pelist = root_pelist allocate( pset%pset(0:npset-1) ) do i = 0,npes/npset-1 k = npset*i !designate the root PE, next PE, prev PE do j = 0,npset-1 if( pe.EQ.pset%pelist(k+j) )then pset%pset(:) = pset%pelist(k:k+npset-1) pset%pos = j pset%root_in_pset = pset%root_pelist(i) if( j.EQ.0 )then pset%prev_in_pset = pset%pelist(k+npset-1) else pset%prev_in_pset = pset%pelist(k+j-1) end if if( j.EQ.npset-1 )then pset%next_in_pset = pset%pelist(k) else pset%next_in_pset = pset%pelist(k+j+1) end if end if end do end do pset%root = pe.EQ.pset%root_in_pset !stack pset%hiWM = 0 !initialize hi-water-mark pset%maxstack = 1000000 !default if( PRESENT(stacksize) )pset%maxstack = stacksize write( out_unit,'(a,i8)' ) & 'MPP_PSET_CREATE: setting stacksize=', pset%maxstack if( pset%root )then allocate( pset%stack(pset%maxstack) ) #ifdef use_CRI_pointers pset%p_stack = LOC(pset%stack) #endif end if pset%initialized = .TRUE. !must be called before using pset call mpp_pset_broadcast_ptr(pset,pset%p_stack) endif call mpp_declare_pelist(root_pelist) if( verbose )then write( errunit,'(a,4i6)' )'MPP_PSET_CREATE: pe, root, next, prev=', & pe, pset%root_in_pset, pset%next_in_pset, pset%prev_in_pset write( errunit,* )'PE ', pe, ' pset=', pset%pset(:) write( out_unit,* )'root pelist=', pset%root_pelist(:) end if end subroutine mpp_pset_create subroutine mpp_pset_delete(pset) type(mpp_pset_type), intent(inout) :: pset integer :: out_unit out_unit = stdout() if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_DELETE: called with uninitialized PSET.' ) !deallocate arrays... deallocate( pset%pelist ) deallocate( pset%root_pelist ) deallocate( pset%pset ) if( pset%root )deallocate( pset%stack ) write( out_unit, '(a,i10)' ) & 'Deleting PSETs... stack high-water-mark=', pset%hiWM !... and set status flag pset%initialized = .FALSE. end subroutine mpp_pset_delete subroutine mpp_send_ptr_scalar( ptr, pe ) integer(POINTER_KIND), intent(in) :: ptr integer, intent(in) :: pe !currently only wraps mpp_send !on some architectures, mangling might occur call mpp_send( ptr, pe, tag=COMM_TAG_1 ) end subroutine mpp_send_ptr_scalar subroutine mpp_send_ptr_array( ptr, pe ) integer(POINTER_KIND), intent(in) :: ptr(:) integer, intent(in) :: pe !currently only wraps mpp_send !on some architectures, mangling might occur call mpp_send( ptr, size(ptr), pe, tag=COMM_TAG_2 ) end subroutine mpp_send_ptr_array subroutine mpp_recv_ptr_scalar( ptr, pe ) integer(POINTER_KIND), intent(inout) :: ptr integer, intent(in) :: pe call mpp_recv( ptr, pe, tag=COMM_TAG_1 ) call mpp_translate_remote_ptr( ptr, pe ) return end subroutine mpp_recv_ptr_scalar subroutine mpp_recv_ptr_array( ptr, pe ) integer(POINTER_KIND), intent(inout) :: ptr(:) integer, intent(in) :: pe integer :: i call mpp_recv( ptr, size(ptr), pe, tag=COMM_TAG_2 ) do i = 1, size(ptr) call mpp_translate_remote_ptr( ptr(i), pe ) end do return end subroutine mpp_recv_ptr_array subroutine mpp_translate_remote_ptr( ptr, pe ) !modifies the received pointer to correct numerical address integer(POINTER_KIND), intent(inout) :: ptr integer, intent(in) :: pe #ifdef use_SGI_GSM !from the MPI_SGI_GLOBALPTR manpage ! POINTER(global_ptr, global_addr) ! INTEGER rem_rank, comm, ierror ! INTEGER(KIND=MPI_ADDRESS_KIND) rem_addr, size, global_addr ! ! CALL MPI_SGI_GLOBALPTR(rem_addr, size, rem_rank, comm, global_ptr, ierror) real :: dummy pointer( p, dummy ) integer :: ierror !length goes in the second argument to MPI_SGI_GLOBALPTR ! according to Kim Mcmahon, this is only used to ensure the requested array ! length is within the valid memory-mapped region. We do not have access to ! the actual array length, so we are only going to set it to 1. This might ! unexpectedly fail on some large model. integer(POINTER_KIND) :: length=1 #ifdef sgi_mipspro return !no translation needed on sgi_mipspro if SMA_GLOBAL_ALLOC is set #endif #ifdef use_libMPI !the MPI communicator was stored in pset%commID !since this routine doesn't take a pset argument, we let the caller store !it in the module global variable commID (see broadcast_ptr and check_ptr) p = ptr call MPI_SGI_GLOBALPTR( dummy, length, pe, commID, ptr, ierror ) if( ierror.EQ.-1 )call mpp_error( FATAL, & 'MPP_TRANSLATE_REMOTE_PTR: unknown MPI_SGI_GLOBALPTR error.' ) #else call mpp_error( FATAL, & 'MPP_TRANSLATE_REMOTE_PTR now only works under -Duse_libMPI' ) #endif #endif return end subroutine mpp_translate_remote_ptr subroutine mpp_pset_sync(pset) !this is a replacement for mpp_sync, doing syncs across !shared arrays without calling mpp_sync type(mpp_pset_type), intent(in) :: pset if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_SYNC: called with uninitialized PSET.' ) #ifdef use_SGI_GSM !assumes npset contiguous PEs starting with root_in_pset call SHMEM_BARRIER( pset%root_in_pset, 0, pset%npset, pSync ) #else !currently does mpp_sync!!! slow!!! !try and make a lightweight pset sync call mpp_sync #endif end subroutine mpp_pset_sync subroutine mpp_pset_broadcast(pset,a) !broadcast value on the root to its sub-threads type(mpp_pset_type), intent(in) :: pset real, intent(inout) :: a integer :: i if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_BROADCAST: called with uninitialized PSET.' ) if( pset%root )then do i = 1,pset%npset-1 call mpp_send( a, pset%pset(i), tag=COMM_TAG_3 ) end do else call mpp_recv( a, pset%root_in_pset, tag=COMM_TAG_3 ) end if call mpp_pset_sync(pset) end subroutine mpp_pset_broadcast subroutine mpp_pset_broadcast_ptr_scalar(pset,ptr) !create a shared array by broadcasting pointer !root allocates memory and passes pointer in !on return all other PSETs will have the pointer to a shared object type(mpp_pset_type), intent(in) :: pset integer(POINTER_KIND), intent(inout) :: ptr integer :: i if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_BROADCAST_PTR: called with uninitialized PSET.' ) commID = pset%commID !pass to mpp_translate_remote_ptr if( pset%root )then do i = 1,pset%npset-1 call mpp_send_ptr( ptr, pset%pset(i) ) end do else call mpp_recv_ptr( ptr, pset%root_in_pset ) end if call mpp_sync_self() end subroutine mpp_pset_broadcast_ptr_scalar subroutine mpp_pset_broadcast_ptr_array(pset,ptr) !create a shared array by broadcasting pointer !root allocates memory and passes pointer in !on return all other PSETs will have the pointer to a shared object type(mpp_pset_type), intent(in) :: pset integer(POINTER_KIND), intent(inout) :: ptr(:) integer :: i if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_BROADCAST_PTR: called with uninitialized PSET.' ) commID = pset%commID !pass to mpp_translate_remote_ptr if( pset%root )then do i = 1,pset%npset-1 call mpp_send_ptr( ptr, pset%pset(i) ) end do else call mpp_recv_ptr( ptr, pset%root_in_pset ) end if call mpp_sync_self() end subroutine mpp_pset_broadcast_ptr_array subroutine mpp_pset_check_ptr(pset,ptr) !checks if the supplied pointer is indeed shared type(mpp_pset_type), intent(in) :: pset #ifdef use_CRI_pointers real :: dummy pointer( ptr, dummy ) #else integer(POINTER_KIND), intent(in) :: ptr #endif #ifdef PSET_DEBUG integer(POINTER_KIND) :: p integer :: i if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_CHECK_PTR: called with uninitialized PSET.' ) commID = pset%commID !pass to mpp_translate_remote_ptr !check if this is a shared pointer p = ptr if( pset%root )then do i = 1,pset%npset-1 call mpp_send_ptr( p, pset%pset(i) ) end do else call mpp_recv_ptr( p, pset%root_in_pset ) end if call mpp_pset_sync(pset) if( p.NE.ptr )call mpp_error( FATAL, & 'MPP_PSET_CHECK_PTR: pointers do not match!' ) #else !do nothing if the debug CPP flag isn't on #endif end subroutine mpp_pset_check_ptr subroutine mpp_pset_segment_array( pset, ls, le, lsp, lep ) !given input indices ls, le, returns indices lsp, lep !so that segments span the range ls:le with no overlaps. !attempts load balance: also some PSETs might get lsp>lep !so that do-loops will be null type(mpp_pset_type), intent(in) :: pset integer, intent(in) :: ls, le integer, intent(out) :: lsp, lep integer :: i if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_SEGMENT_ARRAY: called with uninitialized PSET.' ) #ifdef PSET_DEBUG if( le-ls+1.LT.pset%npset )then write( text,'(3(a,i6))' ) & 'MPP_PSET_ARRAY_SEGMENT: parallel range (', ls, ',', le, & ') is smaller than the number of threads:', pset%npset call mpp_error( WARNING, text ) end if #endif lep = ls-1 !initialize so that lsp is correct on first pass do i = 0,pset%pos lsp = lep + 1 lep = lsp + CEILING( REAL(le-lsp+1)/(pset%npset-i) ) - 1 end do end subroutine mpp_pset_segment_array subroutine mpp_pset_stack_push( pset, ptr, len ) !mpp_malloc specialized for shared arrays !len is the length of the required array !lstack is the stack already in play !user should zero lstack (call mpp_pset_stack_reset) when the stack is to be cleared type(mpp_pset_type), intent(inout) :: pset integer, intent(in) :: len #ifdef use_CRI_pointers real :: dummy pointer( ptr, dummy ) real :: stack(pset%maxstack) pointer( p, stack ) if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_STACK_PUSH: called with uninitialized PSET.' ) if( pset%lstack+len.GT.pset%maxstack )then write( text, '(a,3i12)' ) & 'MPP_PSET_STACK_PUSH: mpp_pset_stack overflow: '// & 'len+lstack.GT.maxstack. len, lstack, maxstack=', & len, pset%lstack, pset%maxstack call mpp_error( FATAL, text ) end if p = pset%p_stack !point stack to shared stack pointer ptr = LOC( stack(pset%lstack+1) ) call mpp_pset_check_ptr(pset,ptr) !make sure ptr is the same across PSETs pset%lstack = pset%lstack + len pset%hiWM = max( pset%hiWM, pset%lstack ) #else integer(POINTER_KIND), intent(out) :: ptr call mpp_error( FATAL, & 'MPP_PSET_STACK_PUSH only works with Cray pointers.' ) #endif end subroutine mpp_pset_stack_push subroutine mpp_pset_stack_reset(pset) type(mpp_pset_type), intent(inout) :: pset !reset stack... will reuse any temporary arrays! USE WITH CARE !next few lines are to zero stack contents... !but it's better noone tries to use uninitialized stack variables! ! integer :: l1, l2 ! real :: mpp_pset_stack(maxstack) ! pointer( p_mpp_pset_stack, mpp_pset_stack ) ! p_mpp_pset_stack = ptr_mpp_pset_stack ! call mpp_pset_array_segment( 1, lstack, l1, l2 ) ! mpp_pset_stack(l1:l2) = 0. if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_STACK_RESET: called with uninitialized PSET.' ) pset%lstack = 0 end subroutine mpp_pset_stack_reset subroutine mpp_pset_print_chksum_1D(pset, caller, array) !print a checksum of an array !pass the whole domain seen by root PSET !add lines to check on shared array? type(mpp_pset_type), intent(in) :: pset character(len=*), intent(in) :: caller real, intent(in) :: array(:) integer :: errunit #ifdef PSET_DEBUG logical :: do_print integer(LONG_KIND) :: chksum if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_PRINT_CHKSUM: called with uninitialized PSET.' ) errunit = stderr() if( pset%root )then do_print = pe.EQ.mpp_root_pe() !set to T to print from all PEs call mpp_set_current_pelist(pset%root_pelist) chksum = mpp_chksum( array ) if( do_print ) & write( errunit, '(a,z18)' )trim(caller)//' chksum=', chksum end if call mpp_set_current_pelist(pset%pelist) #endif return end subroutine mpp_pset_print_chksum_1D subroutine mpp_pset_print_chksum_2D(pset, caller, array) type(mpp_pset_type), intent(in) :: pset character(len=*), intent(in) :: caller real, intent(in) :: array(:,:) real :: array1D( size(array) ) #ifdef use_CRI_pointers pointer( p, array1D ) p = LOC(array) #else array1D = TRANSFER( array, array1D ) #endif call mpp_pset_print_chksum(pset, caller, array1D) end subroutine mpp_pset_print_chksum_2D subroutine mpp_pset_print_chksum_3D(pset, caller, array) type(mpp_pset_type), intent(in) :: pset character(len=*), intent(in) :: caller real, intent(in) :: array(:,:,:) !overload for other ranks real :: array1D( size(array) ) #ifdef use_CRI_pointers pointer( p, array1D ) p = LOC(array) #else array1D = TRANSFER( array, array1D ) #endif call mpp_pset_print_chksum(pset, caller, array1D) end subroutine mpp_pset_print_chksum_3D subroutine mpp_pset_print_chksum_4D(pset, caller, array) type(mpp_pset_type), intent(in) :: pset character(len=*), intent(in) :: caller real, intent(in) :: array(:,:,:,:) real :: array1D( size(array) ) #ifdef use_CRI_pointers pointer( p, array1D ) p = LOC(array) #else array1D = TRANSFER( array, array1D ) #endif call mpp_pset_print_chksum(pset, caller, array1D) end subroutine mpp_pset_print_chksum_4D subroutine mpp_pset_print_stack_chksum( pset, caller ) type(mpp_pset_type), intent(in) :: pset character(len=*), intent(in) :: caller if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_PRINT_STACK_CHKSUM: called with uninitialized PSET.' ) call mpp_pset_print_chksum( pset, trim(caller)//' stack', & pset%stack(1:pset%lstack) ) end subroutine mpp_pset_print_stack_chksum !accessor functions function mpp_pset_root(pset) logical :: mpp_pset_root type(mpp_pset_type), intent(in) :: pset if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_ROOT: called with uninitialized PSET.' ) mpp_pset_root = pset%root end function mpp_pset_root function mpp_pset_numroots(pset) !necessary to export root_pelist: caller needs to pre-allocate integer :: mpp_pset_numroots type(mpp_pset_type), intent(in) :: pset if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_NUMROOTS: called with uninitialized PSET.' ) mpp_pset_numroots = size(pset%root_pelist) end function mpp_pset_numroots subroutine mpp_pset_get_root_pelist(pset,pelist,commID) type(mpp_pset_type), intent(in) :: pset integer, intent(out) :: pelist(:) integer, intent(out), optional :: commID if( .NOT.pset%initialized )call mpp_error( FATAL, & 'MPP_PSET_GET_ROOT_PELIST: called with uninitialized PSET.' ) if( size(pelist).NE.size(pset%root_pelist) )then write( text,'(a,2i6)' ) & 'pelist argument has wrong size: requested, actual=', & size(pelist), size(pset%root_pelist) call mpp_error( FATAL, 'MPP_PSET_GET_ROOT_PELIST: '//text ) end if pelist(:) = pset%root_pelist(:) if( PRESENT(commID) )then #ifdef use_libMPI commID = pset%commID #else call mpp_error( WARNING, & 'MPP_PSET_GET_ROOT_PELIST: commID is only defined under -Duse_libMPI.' ) #endif end if end subroutine mpp_pset_get_root_pelist end module mpp_pset_mod