是否可以对派生的Fortran类型的元素进行OpenMP缩减?

时间:2017-07-27 14:55:07

标签: fortran openmp gfortran reduction derived-types

我正在尝试使用Fortran代码(Gfortran)来使用OpenMP。它是基于粒子的代码,其中数组的索引可以对应于粒子或对。该代码使用派生类型为每个粒子存储多个矩阵。遇到需要使用存储在此派生类型中的矩阵的循环是很常见的。该矩阵可以由多个线程访问。循环还需要减少此派生类型中的元素。我目前必须编写一个临时数组才能进行此减少,然后我将派生类型的元素设置为等于此临时减少数组。如果不使用OpenMP,则不需要临时数组。

问题:是否可以对派生类型的元素进行缩减?我不认为我可以减少整个派生类型,因为我需要访问派生类型中的一些元素来完成工作,这意味着它需要共享。 (从阅读specification我了解到,当使用REDUCTION时,会创建每个列表项的私有副本。)

完整的最小工作示例如下。它可能更小,但我担心删除更多组件可能会简化问题。

PROGRAM TEST_OPEN_MP
USE, INTRINSIC :: iso_fortran_env
USE omp_lib

IMPLICIT NONE 

INTEGER,       PARAMETER                      ::  dp = REAL64
INTEGER,       PARAMETER                      ::  ndim=3
INTEGER,       PARAMETER                      ::  no_partic=100000
INTEGER,       PARAMETER                      ::  len_array=1000000
INTEGER                                       ::  k, i, ii, j, jj
INTEGER,       DIMENSION(1:len_array)         ::  pair_i, pair_j
REAL(KIND=dp), DIMENSION(1:len_array)         ::  pair_i_r, pair_j_r
REAL(KIND=dp), DIMENSION(1:no_partic)         ::  V_0 
REAL(KIND=dp), DIMENSION(1:ndim,1:no_partic)  ::  disp, foovec
REAL(KIND=dp), DIMENSION(1:ndim,1:len_array)  ::  dvx 
REAL(KIND=dp), DIMENSION(1:2*ndim,1:len_array)::  vec 
REAL(KIND=dp), DIMENSION(1:ndim)              ::  disp_ij,temp_vec1,temp_vec2
REAL(KIND=dp), DIMENSION(1:ndim,1:ndim)       ::  temp_ten1,temp_ten2
REAL(KIND=dp), DIMENSION(1:no_partic,1:ndim,1:ndim)::  reduc_ten1
REAL(KIND=dp)                                 ::  sum_check1,sum_check2,cstart,cend

TYPE :: matrix_holder                                                   !<-- The derived type
  REAL(KIND=dp), DIMENSION(1:ndim,1:ndim)      ::  mat1                 !<-- The first element
  REAL(KIND=dp), DIMENSION(1:ndim,1:ndim)      ::  mat2                 !<-- The second element, etc.
END TYPE matrix_holder

TYPE(matrix_holder), DIMENSION(1:no_partic)    ::  matrix

! Setting "random" values to the arrays
DO k = 1, no_partic
  CALL random_number(matrix(k)%mat1(1:ndim,1:ndim))
  CALL random_number(matrix(k)%mat2(1:ndim,1:ndim))
END DO 
CALL random_number(pair_i_r)
CALL random_number(pair_j_r)
CALL random_number(disp)
CALL random_number(vec)
CALL random_number(dvx)
CALL random_number(V_0)
disp = disp*10.d0
vec = vec*100.d0
dvx = dvx*200.d0
V_0 = V_0*10d0
pair_i = FLOOR(no_partic*pair_i_r)+1
pair_j = FLOOR(no_partic*pair_j_r)+1

! Doing the work 
cstart = omp_get_wtime()
!$OMP PARALLEL DO DEFAULT(SHARED) &
!$OMP& PRIVATE(i,j,k,disp_ij,temp_ten1,temp_ten2,temp_vec1,temp_vec2,ii,jj), &
!$OMP& REDUCTION(+:foovec,reduc_ten1), SCHEDULE(static)
DO k= 1, len_array
  i = pair_i(k)
  j = pair_j(k)
  disp_ij(1:ndim) = disp(1:ndim,i)-disp(1:ndim,j)
  temp_vec1 = MATMUL(matrix(i)%mat2(1:ndim,1:ndim),& 
    vec(1:ndim,k))
  temp_vec2 = MATMUL(matrix(j)%mat2(1:ndim,1:ndim),&
    vec(1:ndim,k))
  DO jj=1,ndim
    DO ii = 1,ndim
      temp_ten1(ii,jj) = -disp_ij(ii) * vec(jj,k)
      temp_ten2(ii,jj) = disp_ij(ii) * vec(ndim+jj,k)
    END DO
  END DO
  reduc_ten1(i,1:ndim,1:ndim)=reduc_ten1(i,1:ndim,1:ndim)+temp_ten1*V_0(j) !<--The temporary reduction array
  reduc_ten1(j,1:ndim,1:ndim)=reduc_ten1(j,1:ndim,1:ndim)+temp_ten2*V_0(i)
  foovec(1:ndim,i) = foovec(1:ndim,i) - temp_vec1(1:ndim)*V_0(j)           !<--A generic reduction vector
  foovec(1:ndim,j) = foovec(1:ndim,j) + temp_vec1(1:ndim)*V_0(i)
END DO
!$OMP END PARALLEL DO
cend = omp_get_wtime()

! Checking the results
sum_check1 = 0.d0 
sum_check2 = 0.d0 
DO i = 1,no_partic
  matrix(i)%mat2(1:ndim,1:ndim)=reduc_ten1(i,1:ndim,1:ndim)                !<--Writing the reduction back to the derived type element
  sum_check1 = sum_check1+SUM(foovec(1:ndim,i))
  sum_check2 = sum_check2+SUM(matrix(i)%mat2(1:ndim,1:ndim))
END DO 
WRITE(*,*) sum_check1, sum_check2, cend-cstart

END PROGRAM TEST_OPEN_MP

我能想到的唯一其他选择是删除所有派生类型,并将其替换为示例中与reduc_ten1类似的大型数组。

1 个答案:

答案 0 :(得分:1)

不幸的是,你想要的是不可能的。至少如果我理解你的(对我来说很复杂!)代码是正确的。

问题是你有一个派生类型数组,每个派生类型都有一个数组。你不能参考它。

考虑这个玩具示例:

  type t
    real :: mat(3)
  end type

  integer, parameter :: n = 100, nk = 1000

  type(t) :: parts(n)

  integer :: i

  real :: array(3,n,nk)

  do k = 1, nk
    array(:,:,nk) = k
  end do

  do i = 1, n
    parts(i)%mat = 0
  end do



  !$omp parallel do reduction(+:parts%mat)
  do k = 1, nk
    do i = 1, n
      parts(i)%mat = parts(i)%mat + array(:,i,nk)
    end do
  end do
  !$omp end parallel do
end

英特尔Fortran提出了一个更具体的错误:

reduction6.f90(23): error #6159: A component cannot be an array if the encompassing structure is an array.   [MAT]
  !$omp parallel do reduction(+:parts%mat)
--------------------------------------^
reduction6.f90(23): error #7656: Subobjects are not allowed in this OpenMP* clause; a named variable must be specified.   [PARTS]
  !$omp parallel do reduction(+:parts%mat)
--------------------------------^

请记住,完全没有OpenMP,甚至不允许这样做:

parts%mat = 0

英特尔:

 reduction6.f90(21): error #6159: A component cannot be an array if the  encompassing structure is an array.   [MAT]

gfortran:

Error: Two or more part references with nonzero rank must not be specified at (1)

你必须这样做:

  do i = 1, n
    parts(i)%mat = 0
  end do

上述英特尔报告错误的原因非常相似。

实际上,reduce子句中不允许派生类型组件,只能使用变量名。这就是gfortran报告语法错误的原因。它不期望任何%。英特尔再次提供了更清晰的错误消息。

但是有人可以解决这个问题,比如将它传递给子程序并在那里进行缩减。