是否可以对派生的 Fortran 类型的元素进行 OpenMP 缩减?
Is it possible to do OpenMP reduction over an element of a derived Fortran type?
我正在尝试修改 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
的大型数组。
很遗憾,您想要的是不可能的。至少如果我正确理解你的(对我来说非常复杂!)代码。
问题是您有一个派生类型数组,每个派生类型都有一个数组。你不能引用它。
考虑这个玩具示例:
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
Intel 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
上面Intel报错的原因很相似
实际上reduction子句中不允许使用派生类型组件,只能使用变量名。这就是 gfortran 报告语法错误的原因。它不希望那里有任何 %
。 Intel再次给出更清晰的错误信息。
但是可以解决这个问题,比如将其传递给子例程并在那里进行归约。
我正在尝试修改 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
的大型数组。
很遗憾,您想要的是不可能的。至少如果我正确理解你的(对我来说非常复杂!)代码。
问题是您有一个派生类型数组,每个派生类型都有一个数组。你不能引用它。
考虑这个玩具示例:
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
Intel 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
上面Intel报错的原因很相似
实际上reduction子句中不允许使用派生类型组件,只能使用变量名。这就是 gfortran 报告语法错误的原因。它不希望那里有任何 %
。 Intel再次给出更清晰的错误信息。
但是可以解决这个问题,比如将其传递给子例程并在那里进行归约。