¿Cómo conservo la precisión para un programa Fortran MPI de manera portátil?
precision (3)
Qué tal si:
integer, parameter :: DOUBLE_PREC = kind(0.0d0)
integer, parameter :: SINGLE_PREC = kind(0.0e0)
integer, parameter :: MYREAL = DOUBLE_PREC
if (MYREAL .eq. DOUBLE_PREC) then
MPIREAL = MPI_DOUBLE_PRECISION
else if (MYREAL .eq. SINGLE_PREC) then
MPIREAL = MPI_REAL
else
print *, "Erorr: Can''t figure out MPI precision."
STOP
end if
y use MPIREAL en lugar de MPI_DOUBLE_PRECISION a partir de ese momento.
Tengo un programa Fortran donde especifico el kind
de tipos de datos numéricos en un intento de mantener un nivel mínimo de precisión, independientemente de qué compilador se use para construir el programa. Por ejemplo:
integer, parameter :: rsp = selected_real_kind(4)
...
real(kind=rsp) :: real_var
El problema es que he usado MPI para paralelizar el código y necesito asegurarme de que las comunicaciones MPI especifiquen el mismo tipo con la misma precisión. Estaba usando el siguiente enfoque para mantener la coherencia con el enfoque de mi programa:
call MPI_Type_create_f90_real(4,MPI_UNDEFINED,rsp_mpi,mpi_err)
...
call MPI_Send(real_var,1,rsp_mpi,dest,tag,MPI_COMM_WORLD,err)
Sin embargo, he descubierto que esta rutina de MPI no es particularmente compatible con las diferentes implementaciones de MPI, por lo que en realidad hace que mi programa no sea portátil. Si MPI_Type_create
rutina MPI_Type_create
, entonces me queda confiar en los tipos de datos MPI_REAL
y MPI_DOUBLE_PRECISION
estándar, pero ¿qué MPI_DOUBLE_PRECISION
si ese tipo no es coherente con lo que selected_real_kind
elige como el tipo real que finalmente pasará MPI? ¿Estoy atascado simplemente usando la declaración real
estándar para un tipo de datos, sin ningún atributo de kind
y, si lo hago, tengo garantizado que MPI_REAL
y real
siempre tendrán la misma precisión, independientemente del compilador y la máquina?
ACTUALIZAR:
Creé un programa simple que demuestra el problema que veo cuando mis reales internos tienen una precisión mayor que la que ofrece el tipo MPI_DOUBLE_PRECISION
:
program main
use mpi
implicit none
integer, parameter :: rsp = selected_real_kind(16)
integer :: err
integer :: rank
real(rsp) :: real_var
call MPI_Init(err)
call MPI_Comm_rank(MPI_COMM_WORLD,rank,err)
if (rank.eq.0) then
real_var = 1.123456789012345
call MPI_Send(real_var,1,MPI_DOUBLE_PRECISION,1,5,MPI_COMM_WORLD,err)
else
call MPI_Recv(real_var,1,MPI_DOUBLE_PRECISION,0,5,MPI_COMM_WORLD,&
MPI_STATUS_IGNORE,err)
end if
print *, rank, real_var
call MPI_Finalize(err)
end program main
Si construyo y corro con 2 núcleos, obtengo:
0 1.12345683574676513672
1 4.71241976735884452383E-3998
Ahora cambie el 16 a un 15 en selected_real_kind
y obtengo:
0 1.1234568357467651
1 1.1234568357467651
¿Siempre será seguro usar selected_real_kind(15)
con MPI_DOUBLE_PRECISION
sin importar qué máquina / compilador se use para construir?
Realmente no es una respuesta, pero tenemos el mismo problema y utilizamos algo como esto:
!> Number of digits for single precision numbers
integer, parameter, public :: single_prec = 6
!> Number of digits for double precision numbers
integer, parameter, public :: double_prec = 15
!> Number of digits for extended double precision numbers
integer, parameter, public :: xdble_prec = 18
!> Number of digits for quadruple precision numbers
integer, parameter, public :: quad_prec = 33
integer, parameter, public :: rk_prec = double_prec
!> The kind to select for default reals
integer, parameter, public :: rk = selected_real_kind(rk_prec)
Y luego tenemos una rutina de inicialización donde hacemos:
!call mpi_type_create_f90_real(rk_prec, MPI_UNDEFINED, rk_mpi, iError)
!call mpi_type_create_f90_integer(long_prec, long_k_mpi, iError)
! Workaround shitty MPI-Implementations.
select case(rk_prec)
case(single_prec)
rk_mpi = MPI_REAL
case(double_prec)
rk_mpi = MPI_DOUBLE_PRECISION
case(quad_prec)
rk_mpi = MPI_REAL16
case default
write(*,*) ''unknown real type specified for mpi_type creation''
end select
long_k_mpi = MPI_INTEGER8
Si bien esto no es agradable, funciona razonablemente bien y parece ser utilizable en Cray, IBM BlueGene y clusters Linux convencionales. Lo mejor que puede hacer es presionar a los sitios y proveedores para que lo admitan correctamente en MPI. Por lo que yo sé, se ha corregido en OpenMPI y estaba previsto que se arreglara en MPICH por 3.1.1. Vea las entradas OpenMPI 3432 y 3435 , así como las entradas MPICH 1769 y 1770 .
Utilice el STORAGE_SIZE
intrínseco de Fortran 2008 para determinar el número de bytes que requiere cada número y enviarlos como bytes. Tenga en cuenta que STORAGE_SIZE
devuelve el tamaño en bits, por lo que deberá dividir por 8 para obtener el tamaño en bytes.
Esta solución funciona para mover datos pero no ayuda a usar reducciones. Para eso, tendrá que implementar una operación de reducción definida por el usuario. Si eso es importante para ti, actualizaré mi respuesta con los detalles.
Por ejemplo:
program main
use mpi
implicit none
integer, parameter :: rsp = selected_real_kind(16)
integer :: err
integer :: rank
real(rsp) :: real_var
call MPI_Init(err)
call MPI_Comm_rank(MPI_COMM_WORLD,rank,err)
if (rank.eq.0) then
real_var = 1.123456789012345
call MPI_Send(real_var,storage_size(real_var)/8,MPI_BYTE,1,5,MPI_COMM_WORLD,err)
else
call MPI_Recv(real_var,storage_size(real_var)/8,MPI_BYTE,0,5,MPI_COMM_WORLD,&
MPI_STATUS_IGNORE,err)
end if
print *, rank, real_var
call MPI_Finalize(err)
end program main
Confirmé que este cambio corrige el problema y la salida que veo es:
0 1.12345683574676513672
1 1.12345683574676513672