Fortran 中带掩码的移动平均线

Moving average with mask in Fortran

我必须在 Fortran 中计算维度为 (7320,8520) 的屏蔽数据集的移动平均值。我写了一个接收数据(TS)并输出平均数据(TS_NEW)的子程序。问题是代码 运行 花费的时间太长(它实际上从未完成,尽管没有 运行 进入内存问题)。我想知道是否有一种方法可以使代码更高效。下面是我写的代码:

SUBROUTINE avgwin(ts,winsize,size1,size2,sizelat,sizelon,ts_new)
implicit none
double precision, dimension(size1,size2),INTENT(IN) :: ts
double precision, dimension(winsize,winsize) :: store
double precision, dimension(sizelon,sizelat),INTENT(OUT) :: ts_new
integer :: j,k
integer :: A, B
integer,INTENT(IN) :: winsize,size1,size2,sizelat,sizelon
logical, dimension(size1,size2) :: mask,mask2
double precision :: SUMVAR, COUNTVAR
A=1
B=1

mask = ts > 0 !Mask to highlight all the OK values
mask2 = ts < 0 !Mask to highlight all the values to be discarded
do j=1,sizelat !Looping through latitude
    do k=1,sizelon !Looping through longitude
        if (ALL(mask2(k:k+winsize-1,j:j+winsize-1)) .eqv. .true.) then
            ts_new(B,A) = -100 !Adds a fill value if all the elements are to be discarded
            B=B+1
        else
            SUMVAR = sum(ts(k:k+winsize-1,j:j+winsize-1),     MASK=mask(k:k+winsize-1,j:j+winsize-1))
            COUNTVAR = count(mask(k:k+winsize-1,j:j+winsize-1))
            ts_new(B,A) = SUMVAR/COUNTVAR
            B=B+1
        end if
    end do
    B=1
    A=A+1
end do
END SUBROUTINE

program test
implicit none
double precision, dimension(7320,8520) :: DATA
double precision, dimension(:,:),allocatable :: DATA_NEW
integer :: sizelat, sizelon, i, j, len1, len2, winsize
integer, dimension(3) :: sizes

len1 = 7320
len2 = 8520
do i=1,8520
    do j=1,7320
        DATA(j,i)= i !Just for testing purposes
    end do
end do

sizes(1:3) = (/300,301,302/)
    do w=1,3
       winsize = sizes(w)
       sizelon = len1-winsize+1
       sizelat = len2-winsize+1
       allocate(DATA_NEW(sizelon,sizelat))
       CALL avgwin(DATA,winsize,len1,len2,sizelat,sizelon,DATANEW)
    end do

end program test

虽然不确定这是否符合OP的目的,但如何先沿一个维度收集数据,然后再沿另一维度收集处理后的数据(即部分求和)?例如,如果我们考虑一个更简单的求和 data( 1:L, 1:L ) 而不是移动大小 w 的 window 的问题,可能有三种不同的方法可以实现这一点:

program main
    implicit none
    real, allocatable, dimension(:,:) :: data, direct, part1, part2
    integer :: i1, i2, L, S, w
    real :: t1, t2

    L = 2000
    w = 50
    S = L - w + 1

    allocate( data( L, L ), direct( S, S ), &
              part1( L, S ), part2( S, S ) )

!> test data

    do i2 = 1, L
    do i1 = 1, L
        data( i1, i2 ) = mod( i1 + i2, 2 )
    enddo
    enddo

!> method 1: direct sum (cost = O( S^2 * w^2 ))

    call cpu_time( t1 )
    do i2 = 1, S
    do i1 = 1, S
        direct( i1, i2 ) = sum( data( i1:(i1 + w - 1), i2:(i2 + w - 1) ) )
    enddo
    enddo
    call cpu_time( t2 )
    print *, "time (s) = ", t2 - t1

!> method 2: partial sum (cost = O( S^2 * w * 2 ))

    call cpu_time( t1 )
    do i2 = 1, S
    do i1 = 1, L
        part1( i1, i2 ) = sum( data( i1, i2:(i2 + w - 1) ) )
    enddo
    enddo

    do i2 = 1, S
    do i1 = 1, S
        part2( i1, i2 ) = sum( part1( i1:(i1 + w - 1), i2 ) )
    enddo
    enddo
    call cpu_time( t2 )
    print *, "time (s) = ", t2 - t1

    print *, "error = ", maxval( abs( part2 - direct ) )

!> method 3: an improved version of method 2 (cost = O( S^2 ))

    call cpu_time( t1 )
    do i1 = 1, L
        part1( i1, 1 ) = sum( data( i1, 1:w ) )
        do i2 = 2, S
            part1( i1, i2 ) = part1( i1, i2-1 ) &
                    - data( i1, i2-1 ) + data( i1, i2+w-1 )
        enddo
    enddo

    do i2 = 1, S
        part2( 1, i2 ) = sum( part1( 1:w, i2 ) )
        do i1 = 2, S
            part2( i1, i2 ) = part2( i1-1, i2 ) &
                    - part1( i1-1, i2 ) + part1( i1+w-1, i2 )
        enddo
    enddo
    call cpu_time( t2 )
    print *, "time (s) = ", t2 - t1

    print *, "error = ", maxval( abs( part2 - direct ) )

end program

然后,gfortran-7.2 -O3 test.f90 似乎提供了一些不错的加速:

 time (s) =    9.64789867
 time (s) =   0.345023155
 error =    0.00000000
 time (s) =    8.60958099E-02
 error =    0.00000000

要使用掩码计算移动平均线,类似的方法可能会以某种方式起作用。如果我们搜索网络,可能还有其他(更好的)approaches/libraries这种移动平均线,因为它是很常见的计算...