Pointers OMP并行fortran程序的指针/副本有哪些注意事项

Pointers OMP并行fortran程序的指针/副本有哪些注意事项,pointers,fortran,openmp,Pointers,Fortran,Openmp,我正在开发一个与OMP并行的大型程序,在几个并行化的地方,我必须在使用指针或数据数组副本之间做出选择。我有一种直觉,即选择可能会影响计算时间,但我不确定。在下面给出的示例程序中,我无法识别差异 编译时使用: gfortran paralleltest.f90-fopenmp 运行时使用: >>> ./a.out 8 10000000 Copies (s): 0.835768998 results: 8415334.9722864032 8

我正在开发一个与OMP并行的大型程序,在几个并行化的地方,我必须在使用指针或数据数组副本之间做出选择。我有一种直觉,即选择可能会影响计算时间,但我不确定。在下面给出的示例程序中,我无法识别差异

编译时使用:

gfortran paralleltest.f90-fopenmp

运行时使用:

>>> ./a.out 8 10000000
 Copies (s):     0.835768998    
 results:    8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032     
 Pointers (s):   0.837329030    
 results:    8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032        8415334.9722864032 
在这个例子中,我想知道一些问题:

  • 一般来说,人们会期望在本例中使用的指针和副本之间没有区别吗
  • 假设拷贝的内存开销不是问题,什么情况(如果有)会导致两者之间的显著差异
  • 我是否以正确的方式衡量了这种性能差异?如果没有,还有什么更好的方法来衡量绩效
以下是fortran文件:

! paralleltest.f90

module m_parallel_test

  implicit none

  type :: t_data
     real, allocatable :: x(:)
  end type t_data

  type :: t_data_ptr
     real, pointer :: x(:)
  end type t_data_ptr

contains

  real(kind=8) function aggregate_data(x) result(value)
    real :: x(:)
    integer :: i
    do i=1,size(x)
       value = value + cos(x(i))
    end do
  end function aggregate_data

  subroutine associate_pointer(parent, ptr)
    real, target :: parent(:)
    real, pointer :: ptr(:)
    ptr => parent
  end subroutine associate_pointer

end module m_parallel_test


program main

  use m_parallel_test

  implicit none

  character(len=32) :: arg
  integer, parameter :: n_set = 8
  integer :: n_threads, n_data, i

  type(t_data) :: parent
  type(t_data), allocatable :: copies(:)
  type(t_data_ptr), allocatable :: pointers(:)
  real(kind=8), allocatable :: copies_results(:)
  real(kind=8), allocatable :: pointers_results(:)
  real :: start_time, stop_time

  call get_command_argument(1, arg)
  read(arg, *) n_threads
  call omp_set_num_threads(n_threads)
  allocate(copies(n_set))
  allocate(pointers(n_set))
  allocate(copies_results(n_set))
  allocate(pointers_results(n_set))

  call get_command_argument(2, arg)
  read(arg, *) n_data
  allocate(parent%x(n_data))

  call seed_rng(1)
  call random_number(parent%x)

  do i=1,n_set
     copies(i)%x = parent%x
     call associate_pointer(parent%x, pointers(i)%x)
  end do

  call cpu_time(start_time)
  !$OMP PARALLEL DO
  do i=1,n_set
     copies_results(i) = aggregate_data(copies(i)%x)
  end do
  call cpu_time(stop_time)
  print *, "Copies (s):   ", stop_time - start_time
  print *, "results: ", copies_results

  call cpu_time(start_time)
  !$OMP PARALLEL DO
  do i=1,n_set
     pointers_results(i) = aggregate_data(pointers(i)%x)
  end do
  call cpu_time(stop_time)
  print *, "Pointers (s): ", stop_time - start_time
  print *, "results: ", pointers_results

end program main


subroutine seed_rng(i)
  integer, intent(in) :: i
  integer, allocatable :: iseed(:)
  integer :: n, j
  call random_seed(size=n)
  allocate(iseed(n))
  iseed= [(i + j, j=1, n)]
  call random_seed(put=iseed)
end subroutine seed_rng

该规范不符合要求的原因有两个:

  • 聚合_数据
    中的函数结果
    在被引用之前未定义

  • 在主程序中,
    指针
    元素的指针组件
    x
    在传递到
    aggregate_data
    过程时具有未定义的指针关联状态,因为调用
    associate_pointer
    的第一个实际参数没有目标属性

(您可以使用伪参数调用一个过程,该伪参数的目标属性与相应的实际参数不匹配,但当您这样做时,与该伪参数关联的指针(例如
ptr
inside
associate_pointer
)在过程执行完成时将变得未定义。)

程序中的错误可以通过为主程序中的
parent
变量指定TARGET属性,并在
aggregate\u data
中的循环之前粘贴适当的赋值语句来纠正

除了正确性之外,两个循环之间的唯一区别在于传递给
聚合\u data
的实际参数-一个循环传递由可分配组件指定的数组,一个循环传递由指针组件指定的数组。该过程将该数组参数视为假定的形状数组伪参数,而不连续-给定假定形状数组的典型实现,编译器无需执行任何不同的传递指针与可分配指针的操作

(如果聚合_数据的伪参数是显式或隐式连续的,则编译器可能需要在调用之前复制指针数组,因为指针可以与非连续数组相关联。可分配数组始终是连续的。)

两个循环都将其结果写入一个不同类型的非目标可分配数组,并将其写入实际的参数组件,因此赋值语句中不能有任何别名

两个实际参数都没有在循环中被修改,因此与在不同迭代中指定相同数组的指针参数相关的潜在别名问题是没有意义的(此外,从编译器的角度来看——这是程序员的责任——不必在意)

总之,我希望为这两个循环发出相同的机器指令。类似的时机也不足为奇

如果在循环中执行不同的工作,则结果可能会有所不同-如果使用了别名或连续性

两种方法的不同之处在于设置可分配数组组件参数所需的数据复制。但这(有意地?)超出了时间机制


从Fortran 2003开始(此代码需要),除非您需要引用语义,否则不要使用指针。

对您的实际任务没有帮助,但我将指出您上面显示的gfortran comand行在优化代码方面几乎没有作用。至少,您可能希望将
-O
添加到命令行。@HighPerformanceMark,谢谢您的评论。我的问题可能源于缺乏经验,但我试图理解让多个线程访问共享内存与让多个线程访问内存副本是否有明显的性能差异。在大多数情况下,它只是在阅读记忆。我在解析它,向你询问答案。你指的是什么错误?当我编译时,我没有看到任何错误或警告(gcc 10.2,std=gnu),当我运行时,我也没有看到任何错误。当使用-std=F2018编译时,我也没有看到任何错误。如果您提供了您唯一可以给出“可能”的答案。这将取决于你正在做的事情的细节,唯一确定的方法是比较这两种方法。另外,请不要无限期地使用Real(8),我会注意到通常最好避免使用Fortran中的指针。Real(8)是不可移植的。请参见示例,但这是一个经常出现的主题-我们已经看到了它导致问题的问题。大多数指针的使用也可以通过使用可分配数组或数组节来实现。指针的使用有许多独特的可能的bug。通过避免它们的使用,您可以避免这些bug,而经常提到的功能(但并非总是)也可以避免它们的需要。