Arrays FORTRAN:通过指针矩阵访问数组,性能
我在这里遇到了使用指针的问题。在此之前,我有一个性能问题。假设有这样一个2D矩阵:Arrays FORTRAN:通过指针矩阵访问数组,性能,arrays,performance,pointers,fortran,Arrays,Performance,Pointers,Fortran,我在这里遇到了使用指针的问题。在此之前,我有一个性能问题。假设有这样一个2D矩阵: 0.0 0.0 0.0..... 0.0 0.7 0.5..... 0.0 0.5 0.8..... 0.0 0.3 0.8..... 我需要计算这东西的梯度。因此,对于每个数字,我需要这个数字以及这个2D矩阵的所有4个最近邻。除此之外,第一行和最后一行和第列都是0 现在我有两种方法: 直接制作这样一个NxN矩阵并计算梯度。完全按照描述。这里的内存使用是nxreal*8,循环从计算(2,2)元
0.0 0.0 0.0.....
0.0 0.7 0.5.....
0.0 0.5 0.8.....
0.0 0.3 0.8.....
我需要计算这东西的梯度。因此,对于每个数字,我需要这个数字以及这个2D矩阵的所有4个最近邻。除此之外,第一行和最后一行和第列都是0
现在我有两种方法:
program stencil
implicit none
type pp
real*8, pointer :: ptr
endtype pp
type(pp), allocatable :: parray(:,:)
real*8, allocatable, target :: array(:)
real*8, allocatable :: grad(:,:,:), direct(:,:)
integer, parameter :: n = 5000
integer :: i, j
integer :: clock_rate, clock_start, clock_stop
allocate(array(n**2+1))
allocate(parray(0:n+1, 0:n+1))
allocate(grad(2, n, n))
call random_number(array)
array(n**2+1) = 0
do i = 0, n + 1
parray(0,i)%ptr => array(n**2+1)
parray(n+1,i)%ptr => array(n**2+1)
parray(i,0)%ptr => array(n**2+1)
parray(i,n+1)%ptr => array(n**2+1)
enddo
do i = 1, n
do j = 1, n
parray(i,j)%ptr => array((i-1) * n + j)
enddo
enddo
!now stencil
call system_clock(count_rate=clock_rate)
call system_clock(count=clock_start)
do j = 1, n
do i = 1, n
grad(1, i, j) = (parray(i + 1,j)%ptr - parray(i - 1,j)%ptr)/2.D0
grad(2, i, j) = (parray(i,j + 1)%ptr - parray(i,j - 1)%ptr)/2.D0
enddo
enddo
call system_clock(count=clock_stop)
print *, "pointer, time cost= ", real(clock_stop-clock_start)/real(clock_rate)
deallocate(array)
deallocate(parray)
allocate(direct(0:n+1, 0:n+1))
call random_number(direct)
do i = 0, n + 1
direct(0,i) = 0
direct(n+1,i) = 0
direct(i,0) = 0
direct(i,n+1) = 0
enddo
!now stencil directly
call system_clock(count_rate=clock_rate)
call system_clock(count=clock_start)
do j = 1, n
do i = 1, n
grad(1, i, j) = (direct(i + 1,j) - direct(i - 1,j))/2.D0
grad(2, i, j) = (direct(i,j + 1) - direct(i,j - 1))/2.D0
enddo
enddo
call system_clock(count=clock_stop)
print *, "direct, time cost= ", real(clock_stop-clock_start)/real(clock_rate)
endprogram stencil
结果(o0):
指针,时间成本=2.170000
直接,时间成本=1.127000
结果(o2):
指针,时间成本=0.5110000
直接,时间成本=9.49999E-02
所以FORTRAN指针的速度要慢得多。斯特凡早些时候指出了这一点。现在我想知道是否有改进的余地。到目前为止,我知道,如果我用c来做,差别应该不会这么大 首先,我必须道歉,因为我误解了指针在Fortran中的工作方式
最后,我对这个话题非常感兴趣,我自己做了一个测试。它基于一个数组,数组的周围有一个零 声明:
real, dimension(:,:), allocatable, target :: array
real, dimension(:,:,:), allocatable :: res
real, dimension(:,:), pointer :: p1, p2, p3, p4
allocate(array(0:n+1, 0:n+1), source=0.)
allocate(res(n,n,2), source=0.)
do j = 1, n
do i = 1, n
res(i,j,1) = array(i+1,j) - array(i-1,j)
res(i,j,2) = array(i,j+1) - array(i,j-1)
end do
end do
res(:,:,1) = array(2:n+1,1:n) - array(0:n-1,1:n)
res(:,:,2) = array(1:n,2:n+1) - array(1:n,0:n-1)
p1 => array(0:n-1,1:n)
p2 => array(1:n,2:n+1)
p3 => array(2:n+1,1:n)
p4 => array(1:n,0:n-1)
res(:,:,1) = p3 - p1
res(:,:,2) = p2 - p4
现在的方法是:
循环:
real, dimension(:,:), allocatable, target :: array
real, dimension(:,:,:), allocatable :: res
real, dimension(:,:), pointer :: p1, p2, p3, p4
allocate(array(0:n+1, 0:n+1), source=0.)
allocate(res(n,n,2), source=0.)
do j = 1, n
do i = 1, n
res(i,j,1) = array(i+1,j) - array(i-1,j)
res(i,j,2) = array(i,j+1) - array(i,j-1)
end do
end do
res(:,:,1) = array(2:n+1,1:n) - array(0:n-1,1:n)
res(:,:,2) = array(1:n,2:n+1) - array(1:n,0:n-1)
p1 => array(0:n-1,1:n)
p2 => array(1:n,2:n+1)
p3 => array(2:n+1,1:n)
p4 => array(1:n,0:n-1)
res(:,:,1) = p3 - p1
res(:,:,2) = p2 - p4
数组分配:
real, dimension(:,:), allocatable, target :: array
real, dimension(:,:,:), allocatable :: res
real, dimension(:,:), pointer :: p1, p2, p3, p4
allocate(array(0:n+1, 0:n+1), source=0.)
allocate(res(n,n,2), source=0.)
do j = 1, n
do i = 1, n
res(i,j,1) = array(i+1,j) - array(i-1,j)
res(i,j,2) = array(i,j+1) - array(i,j-1)
end do
end do
res(:,:,1) = array(2:n+1,1:n) - array(0:n-1,1:n)
res(:,:,2) = array(1:n,2:n+1) - array(1:n,0:n-1)
p1 => array(0:n-1,1:n)
p2 => array(1:n,2:n+1)
p3 => array(2:n+1,1:n)
p4 => array(1:n,0:n-1)
res(:,:,1) = p3 - p1
res(:,:,2) = p2 - p4
指针:
real, dimension(:,:), allocatable, target :: array
real, dimension(:,:,:), allocatable :: res
real, dimension(:,:), pointer :: p1, p2, p3, p4
allocate(array(0:n+1, 0:n+1), source=0.)
allocate(res(n,n,2), source=0.)
do j = 1, n
do i = 1, n
res(i,j,1) = array(i+1,j) - array(i-1,j)
res(i,j,2) = array(i,j+1) - array(i,j-1)
end do
end do
res(:,:,1) = array(2:n+1,1:n) - array(0:n-1,1:n)
res(:,:,2) = array(1:n,2:n+1) - array(1:n,0:n-1)
p1 => array(0:n-1,1:n)
p2 => array(1:n,2:n+1)
p3 => array(2:n+1,1:n)
p4 => array(1:n,0:n-1)
res(:,:,1) = p3 - p1
res(:,:,2) = p2 - p4
虽然后两种方法确实依赖于额外的零层,但循环可以引入一些条件来处理这些问题
时间安排很有趣:
虽然数组和指针分配产生大致相同的计时,但循环构造(注意循环顺序!这是5!!!)是最快的方法
更新:我试图从您的代码中挤出一点性能,发现了一件小事。您的代码在
0.95s
和0.30s
中使用-O2
执行(使用n=10000
)
转换矩阵以获得更线性的内存访问,指针部分的运行时间为0.50s
parray(i,j)%ptr => array((j-1) * n + i)
IMHO,问题是缺少关于指针的信息,这禁止了额外的优化。使用
-O3-fopt info missed
您会收到关于未知对齐和非连续访问的投诉。与我的结果相比,额外的因素2应该来自这样一个事实,即您使用的是双精度,而我的代码是用单精度编写的…我接受Stefan的答案作为最佳答案。但我个人想为讨论和我自己的问题得出一个结论
有关FORTRAN指针,请参阅Stefan的答案。尝试并测量它(并报告结果:)。你对
类型warp
是什么意思?我们大多数Fortran程序员(不管怎样,这一个,我不能代表其余的)都想知道为什么你会考虑使用指针进行N点模具操作。我很难看出你的选择2有什么优势,甚至把性能问题放在一边。使用0的光环填充阵列,以便可以在没有边缘特殊情况的情况下执行模具操作,这是一种完全标准的方法。天哪,在现代Fortran中,您甚至可以从(0,N+1)
索引数组元素,并在(1,N)
@HighPerformanceMark上运行模具,因为您需要一种处理边界条件的通用方法。