Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/arrays/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Arrays FORTRAN:通过指针矩阵访问数组,性能_Arrays_Performance_Pointers_Fortran - Fatal编程技术网

Arrays FORTRAN:通过指针矩阵访问数组,性能

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)元

我在这里遇到了使用指针的问题。在此之前,我有一个性能问题。假设有这样一个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)元素开始,然后(2,3)

  • 制作一个(N-2)x(N-2)+1数组和一个NxN指针矩阵(此时使用type)。数组的(N-2)x(N-2)元素将存储除边框上的0.0之外的数字。的最后一个元素是0.0。对于指针矩阵,边界上的所有元素都将指向数组的最后一个元素0.0。其他指针应该指向它们应该指向的位置

  • 这里有一个性能问题,因为我处理的矩阵可能非常大,或者可能是3D的

    对于方法1,没有什么可说的,因为它只是一个简单的方法

    对于方法2,我想知道编译器是否能够正确处理这个问题。因为根据我到目前为止的理解,每个FORTRAN指针都像一个结构。如果是这种情况,FORTRAN指针比c指针慢,因为它不仅仅是一个简单的反引用?我还想知道指针的类型扭曲是否会降低性能(创建指针矩阵需要这种扭曲)。我应该放弃方法2,因为它应该更慢,这是一个特别的原因吗

    让我们以windows上的IVF、Linux上的gfortran和ifort为例。因为它可能依赖于编译器

    更新: 感谢Stefan的代码。我自己也写了

    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指针不同于C指针。FORTRAN标准似乎旨在使数组指针成为数组的“子节点”。因此,FORTRAN中的“指针数组”与C中的情况不同,几乎毫无意义。请阅读Stefan的代码,了解使用FORTRAN指针的详细信息。此外,FORTRAN中的“指针数组”是可能的,但它的低性能肯定不是简单的解引用

  • 使用带有循环展开的直接访问可以提高计算的性能。请在Stefan的代码中查找详细信息。根据Stefan的评论,使用直接访问时,编译器优化可以做得更好。我认为这就是为什么人们不用指针来解决模具问题

  • 使用指针处理模板的想法是减少内存开销,并使边界条件非常灵活。但目前它不是性能的选择。主要原因是“非连续”内存访问和编译器优化无法在不了解指针模式的情况下执行


  • 有关FORTRAN指针,请参阅Stefan的答案。

    尝试并测量它(并报告结果:)。你对
    类型warp
    是什么意思?我们大多数Fortran程序员(不管怎样,这一个,我不能代表其余的)都想知道为什么你会考虑使用指针进行N点模具操作。我很难看出你的选择2有什么优势,甚至把性能问题放在一边。使用0的光环填充阵列,以便可以在没有边缘特殊情况的情况下执行模具操作,这是一种完全标准的方法。天哪,在现代Fortran中,您甚至可以从
    (0,N+1)
    索引数组元素,并在
    (1,N)
    @HighPerformanceMark上运行模具,因为您需要一种处理边界条件的通用方法。