【问题标题】:Determine assumed-shape array strides at runtime在运行时确定假定形状的数组步长
【发布时间】:2014-04-11 00:59:49
【问题描述】:

在英特尔 Fortran 等现代 Fortran 编译器中是否可以在运行时确定数组步长?例如,我可能想对数组部分执行快速傅里叶变换 (FFT):

program main

    complex(8),allocatable::array(:,:)

    allocate(array(17, 17))
    array = 1.0d0

    call fft(array(1:16,1:16))

contains

    subroutine fft(a)  
        use mkl_dfti

        implicit none

        complex(8),intent(inout)::a(:,:)

        type(dfti_descriptor),pointer::desc
        integer::stat

        stat = DftiCreateDescriptor(desc, DFTI_DOUBLE, DFTI_COMPLEX, 2, shape(a) )
        stat = DftiCommitDescriptor(desc)
        stat = DftiComputeForward(desc, a(:,1))
        stat = DftiFreeDescriptor(desc)

    end subroutine  

end program

但是,需要明确告知 MKL Dfti* 例程的数组步幅。 翻阅参考手册,我没有发现任何返回步幅信息的内在函数。 一些有趣的资源是herehere,它们讨论了是否复制数组部分以及英特尔 Fortran 如何在内部处理数组。 我宁愿不局限于英特尔目前使用其数组描述符的方式。

如何计算步幅信息?请注意,通常我希望fft 例程(或任何类似例程)不需要传入有关数组的任何其他信息。

编辑:

我已经验证了在这种情况下不是创建了一个临时数组,这是我在 Intel(R) Visual Fortran Compiler XE 14.0.2.176 [Intel( R) 64],禁用优化并将堆数组设置为 0。

program main
    implicit none

    real(8),allocatable::a(:,:)

    pause

    allocate(a(8192,8192))

    pause

    call random_number(a)

    pause

    call foo(a(:4096,:4096))

    pause

    contains

    subroutine foo(a)
        implicit none

        real(8)::a(:,:)

        open(unit=16, file='a_sum.txt')

        write(16, *) sum(a)

        close(16)

    end subroutine

end program

监控内存使用情况,很明显永远不会创建一个临时数组。

编辑 2:

module m_foo
    implicit none

contains

    subroutine foo(a)
        implicit none

        real(8),contiguous::a(:,:)

        integer::i, j

        open(unit=16, file='a_sum.txt')

        write(16, *) sum(a)

        close(16)        

        call nointerface(a)

    end subroutine

end module

subroutine nointerface(a)
    implicit none

    real(8)::a(*)

end subroutine

program main
    use m_foo

    implicit none

    integer,parameter::N = 8192
    real(8),allocatable::a(:,:)

    integer::i, j
    real(8)::count

    pause

    allocate(a(N, N))

    pause

    call random_number(a)

    pause

    call foo(a(:N/2,:N/2))

    pause

end program

编辑 3:

该示例说明了我想要实现的目标。有一个 16x16 的连续数组,但我只想转换上面的 4x4 数组。第一次调用只是传入数组部分,但它不会在数组的左上角返回一个。第二个调用设置适当的步幅,a 随后包含正确的上 4x4 数组。上层 4x4 数组相对于整个 16x16 数组的步幅不是一个

program main
    implicit none

    complex(8),allocatable::a(:,:)

    allocate(a(16,16))

    a = 0.0d0
    a(1:4,1:4) = 1.0d0

    call fft(a(1:4,1:4))

    write(*,*) a(1:4,1:4)

    pause

    a = 0.0d0
    a(1:4,1:4) = 1.0d0

    call fft_stride(a(1:4,1:4), 1, 16)

    write(*,*) a(1:4,1:4)

    pause

    contains

    subroutine fft(a)  !{{{
        use mkl_dfti

        implicit none

        complex(8),intent(inout)::a(:,:)

        type(dfti_descriptor),pointer::desc
        integer::stat

        stat = DftiCreateDescriptor(desc, DFTI_DOUBLE, DFTI_COMPLEX, 2, shape(a) )
        stat = DftiCommitDescriptor(desc)
        stat = DftiComputeForward(desc, a(:,1))
        stat = DftiFreeDescriptor(desc)

    end subroutine  !}}}

    subroutine fft_stride(a, s1, s2)  !{{{
        use mkl_dfti

        implicit none


        complex(8),intent(inout)::a(:,:)
        integer::s1, s2

        type(dfti_descriptor),pointer::desc
        integer::stat

        integer::strides(3)

        strides = [0, s1, s2]

        stat = DftiCreateDescriptor(desc, DFTI_DOUBLE, DFTI_COMPLEX, 2, shape(a) )
        stat = DftiSetValue(desc, DFTI_INPUT_STRIDES, strides)
        stat = DftiCommitDescriptor(desc)
        stat = DftiComputeForward(desc, a(:,1))
        stat = DftiFreeDescriptor(desc)

    end subroutine  !}}}  

end program

【问题讨论】:

  • 我怀疑,当 MKL FFT 例程谈论步幅时,他们真正关心的是您正在执行的 FFT 的数据类型和类型,即您是否正在对真实或复杂数据执行 ND FFT。
  • 根据文档,DftiCreateDescriptor 将使用无填充的默认假设,如果您最终将假设形状传递给低级例程,则会发生这种情况。如果你想传递一块array,那么传递整个数组并设置步幅将比传递数组切片更有效,如果它不连续,则会创建一个临时数组。
  • 您确定它会临时创建一个数组吗?我想如果有一个明确的接口就没有临时的:nf.nci.org.au/facilities/software/FORTRAN/Intel10/doc/main_for/… 见右下角。
  • @Yossarian 实际上他们确实允许输入和输出内存以非常通用的方式布局,只要有规律的步幅。我已经使用 C 接口确认了这一点。
  • @bdforbes 一旦您进入 mkl 例程,就会创建临时数组,当然不在您的代码中,因为您的虚拟参数是一个假定的形状。

标签: arrays fortran


【解决方案1】:

我猜你会感到困惑,因为你通过给它a(:,1) 来处理 MKL 函数 DftiComputeForward 的显式接口。这是连续的,不需要临时数组。但是,这是错误的,低级例程将获取整个数组,这就是为什么如果您指定步幅,您会看到它有效。由于DftiComputeForward 执行数组complex(kind), intent inout :: a(*),因此您可以通过外部子程序传递它来工作。

program ...
call fft(4,4,a(1:4,1:4))
end program

subroutine fft(m,n,a)  !{{{
use mkl_dfti

implicit none

complex(8),intent(inout)::a(*)
integer :: m, n

type(dfti_descriptor),pointer::desc
integer::stat

stat = DftiCreateDescriptor(desc, DFTI_DOUBLE, DFTI_COMPLEX, 2, (/m,n/) )
stat = DftiCommitDescriptor(desc)
stat = DftiComputeForward(desc, a)
stat = DftiFreeDescriptor(desc)

end subroutine !}}}

这将在进入子程序时创建一个临时数组。一个更有效的解决方案确实是 st​​rides:

program ...
call fft_strided(4,4,a,16)
end program

subroutine fft_strided(m,n,a,lda)  !{{{
use mkl_dfti

implicit none

complex(8),intent(inout)::a(*)
integer :: m, n, lda

type(dfti_descriptor),pointer::desc
integer::stat

integer::strides(3)

strides = [0, 1, lda]

stat = DftiCreateDescriptor(desc, DFTI_DOUBLE, DFTI_COMPLEX, 2, (/m,n/) )
stat = DftiSetValue(desc, DFTI_INPUT_STRIDES, strides)
stat = DftiCommitDescriptor(desc)
stat = DftiComputeForward(desc, a)
stat = DftiFreeDescriptor(desc)

end subroutine !}}}

【讨论】:

  • 感谢您的总结,我之前可以更清楚。我绝对想要在不必要的时候复制数组。我想除非有人有确定步幅的解决方案,否则我只需要像上面的示例一样明确。
  • @bdforbes:如果你想在没有临时副本的情况下对数组的一部分进行操作,你必须使用 strides,这就是它们的用途。
  • 我在发布我的问题时很清楚这一点,但似乎需要一段时间才能说服其他人!
【解决方案2】:

Tho 例程DftiComputeForward 接受一个假定大小的数组。如果您传递复杂且不连续的内容,则必须在传递时制作副本。编译器可以在运行时检查副本是否真的需要。在任何情况下,步幅始终为 1,因为这将是 MKL 例程将看到的步幅。

在您的情况下,您传递A(:,something),这是一个连续的部分,前提是 A 是连续的。如果 A 不连续,则必须制作副本。步幅始终为 1。

【讨论】:

  • 那么DFTI_INPUT_STRIDESDFTI_OUTPUT_STRIDES 配置参数为什么存在呢?必须有某种方法来利用它们。我已经使用 C 接口完成了。
  • 因为您可以将指针传递给 C 2D 数组(实际上是 1D n x m 数组)中的列。该列是不连续的。 C 数组要简单得多,只是指针。 C 不会为您复制任何连续的列或行。
  • 请注意,您传递的是一维数组。
  • @bdforbes 您实际上可以传递数组名称,这不会导致创建临时文件,但在这种情况下,如果您希望 mkl 例程使用一部分,则必须设置步幅那个数组。
  • @bdforbes 通过将参数重命名为您的子例程 b(或类似),它可能有助于减少虚拟 a 和实际 a 之间的混淆。这将使我们更容易澄清两个数组之间的关联。
【解决方案3】:

这里的一些答案不理解fortran strides和memory strides之间的区别(尽管它们是相关的)。

除了您在此处的具体情况之外,要为未来的读者回答您的问题 - 仅在 fortran 中查找数组跨步似乎并不难,但可以使用较新编译器中的互操作性功能通过 C 来完成。

您可以在 C 中执行此操作:

#include "stdio.h"
size_t c_compute_stride(int * x, int * y)
{
    size_t px = (size_t) x;
    size_t py = (size_t) y;
    size_t d = py-px;
    return d;
}

然后在 fortran 中对数组的前两个元素调用此函数,例如:

program main
    use iso_c_binding
    implicit none

    interface
        function c_compute_stride(x, y) bind(C, name="c_compute_stride")
            use iso_c_binding
            integer :: x, y
            integer(c_size_t) :: c_compute_stride
        end function
    end interface

    integer, dimension(10) :: a
    integer, dimension(10,10) :: b

    write(*,*) find_stride(a)
    write(*,*) find_stride(b(:,1))
    write(*,*) find_stride(b(1,:))

    contains

    function find_stride(x)
        integer, dimension(:) :: x
        integer(c_size_t) :: find_stride
        find_stride = c_compute_stride(x(1), x(2))
    end function

end program

这将打印出来:

                4
                4
               40

【讨论】:

  • 谢谢,这很好。它也可以通过 loc 内在函数在 Fortran 中完成,尽管这通常是编译器扩展。
【解决方案4】:

简而言之:假定形状数组的步幅总是 1。

再长一点:当您将数组的一部分传递给采用假定形状数组的子例程时,正如您在此处所看到的,那么子例程对数组的原始大小一无所知。如果您查看子例程中虚拟参数的上限和下限,您会发现它们始终是数组部分的大小和 1。

integer, dimension(10:20) :: array
integer :: i

array = [ (i, i=10,20) ]
call foo(array(10:20:2))

subroutine foo(a)
    integer, dimension(:) :: a
    integer :: i

    print*, lbound(a), ubound(a)
    do i=lbound(a,1), ubound(a,2)
        print*, a(i)
    end do

end subroutine foo

这给出了输出:

1 6
10 12 14 16 18 20

因此,即使您的数组索引从 10 开始,当您传递它(或它的一部分)时,子例程也会认为索引从 1 开始。类似地,它认为步幅是 1。您可以给出一个下限到虚拟参数:

integer, dimension(10:) :: a

这将使lbound(a) 10 和ubound(a) 15。但不可能让假定形状的数组大步前进。

【讨论】:

  • 查看我的编辑,数组临时为数组部分创建,这意味着步幅信息必须与数组一起传递。
猜你喜欢
  • 2018-08-13
  • 1970-01-01
  • 2017-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-04
  • 2014-05-08
  • 2011-07-03
相关资源
最近更新 更多