Как сохранить точность для программы Fortran MPI портативным способом?
У меня есть программа Fortran, где я указываю kind
числовых типов данных в попытке сохранить минимальный уровень точности, независимо от того, какой компилятор используется для сборки программы. Например:
integer, parameter :: rsp = selected_real_kind(4)
...
real(kind=rsp) :: real_var
Проблема в том, что я использовал MPI для распараллеливания кода, и мне нужно убедиться, что связь MPI указывает тот же тип с той же точностью. Я использовал следующий подход, чтобы оставаться в соответствии с подходом в моей программе:
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)
Однако я обнаружил, что эта процедура MPI не особенно хорошо поддерживается для разных реализаций MPI, поэтому она фактически делает мою программу не переносной. Если я опускаю подпрограмму MPI_Type_create
, тогда мне остается полагаться на стандартные типы данных MPI_REAL
и MPI_DOUBLE_PRECISION
, но что, если этот тип не соответствует тому, что selected_real_kind
выбирает как реальный тип, который в конечном итоге проходить MPI? Я просто придерживался стандартного объявления real
для типа данных, без атрибута kind
, и если я это сделаю, я гарантирую, что MPI_REAL
и real
всегда будут иметь одинаковую точность, независимо от того, компилятор и машина?
UPDATE:
Я создал простую программу, которая демонстрирует проблему, которую я вижу, когда мои внутренние значения имеют более высокую точность, чем то, что предоставляется типом 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
Если я создам и запускаю с двумя ядрами, я получаю:
0 1.12345683574676513672
1 4.71241976735884452383E-3998
Теперь измените значение 16 на 15 в selected_real_kind
, и я получаю:
0 1.1234568357467651
1 1.1234568357467651
Всегда ли безопасно использовать selected_real_kind(15)
с MPI_DOUBLE_PRECISION
независимо от того, какой компьютер/компилятор используется для сборки?
Ответы
Ответ 1
Используйте встроенный Fortran 2008 STORAGE_SIZE
, чтобы определить количество байтов, которое требуется каждому номеру и отправить в виде байтов. Обратите внимание, что STORAGE_SIZE
возвращает размер в битах, поэтому вам нужно разделить на 8, чтобы получить размер в байтах.
Это решение работает для перемещения данных, но не помогает вам использовать сокращения. Для этого вам придется реализовать пользовательскую операцию сокращения. Если это важно для вас, я уточню свой ответ с подробностями.
Например:
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
Я подтвердил, что это изменение исправляет проблему, и я вижу:
0 1.12345683574676513672
1 1.12345683574676513672
Ответ 2
Не совсем ответ, но у нас есть одна и та же проблема и используйте что-то вроде этого:
!> 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)
И затем выполните процедуру инициализации, где мы делаем:
!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
Хотя это нехорошо, он работает достаточно хорошо и, по-видимому, можно использовать в Cray, IBM BlueGene и обычных Linux-кластерах.
Самое лучшее, что нужно сделать, это надавить на сайты и поставщиков, чтобы правильно поддерживать это в MPI. Насколько я знаю, он был исправлен в OpenMPI и планируется установить в MPICH на 3.1.1. См. Билеты OpenMPI 3432 и 3435, а также как билеты MPICH 1769 и 1770.
Ответ 3
Как насчет:
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
и затем используйте MPIREAL вместо MPI_DOUBLE_PRECISION с этого момента.