【问题标题】:Use pointer to refer function or array in Fortran在 Fortran 中使用指针来引用函数或数组
【发布时间】:2018-02-03 18:59:49
【问题描述】:

问题是编写允许使用相同名称的变量的代码,这些变量可以根据条件引用数组或函数。

更详细地说:我有 real*8 函数,它需要 4 个整数索引。这种函数的计算是相当昂贵的。返回此函数的值被使用了数百万次。最明显的解决方案 - 计算一次并使用 4 维数组而不是函数。它可以节省大量时间。

但是当任务的大小增加时,不可能将这样的数组存储在内存中。所以要独立于硬件,需要关闭存储在内存中,改为使用函数。

我想到的唯一一种解决方案是将abstract interface 与“虚拟函数”一起使用,该函数除了按四个索引返回数组的值之外什么都不做。

首先,需要定义抽象接口:

abstract interface
   real(kind=rglu) function integral(a,b,c,d)
   use glob, only: iglu,rglu
   integer(kind=iglu), intent(in) :: a,b,c,d
   end function integral
end interface
procedure (integral), pointer :: R=>null()

接下来,编写函数:

real(kind=rglu) function stored_int(a,b,c,d) result(ret)
implicit none
integer(kind=iglu), intent(in) :: a,b,c,d

ret=hR(a,b,c,d); return
end function stored_int

然后我们会以如下方式使用它:

       if (storeIntegrals) then
            do i = 1,N
            do j = 1,N
            do a = 1,N
            do b = 1,N
                hR(i,j,a,b)=my_integral(i,j,a,b)
            enddo
            enddo
            enddo
            enddo
            R=>stored_int
       else
            R=>my_integral
       endif

函数my_integral是我们需要用数组替换。 不幸的是,这种方法表现出非常糟糕的性能。 编译ifort -O3 -fpp -openmp 在四个内核上,它给出的结果比相同的代码差两倍,但有一个数组(没有虚函数)。

还有其他变体可以解决这个问题吗?

【问题讨论】:

  • 顺便说一句,将return 放入 Fortran 中的每个函数是没有意义的。
  • 我以前放return,如果不影响性能我会继续写。

标签: fortran intel-fortran


【解决方案1】:

您可以尝试的一件事是将integralstored_intmy_integral 声明为 BIND(C) 并让它们的 4 个参数按值传递。这可能会导致stored_int 被更快地调用。

如果做不到这一点,仍然有一些事情可能对你有用。您可以尝试创建包含hR 作为组件R 的用户定义类型Tarray,并键入包含my_integeral 作为组件RTfun。那么访问hR 元素的语法将与调用函数my_integral 的语法相同。您只需要维护一个代码库,它将被移动到一个INCLUDE 文件中。然后,您可以通过通用名称调用其中一个。这是这样一个INCLUDE 文件:

! sub.i90
subroutine sub1(T1,k,mess)
   implicit none
   type(T) T1
   integer k
   character(*) mess
   write(*,'(a)') mess
   write(*,'(*(g0:1x))') T1%R(k)
end subroutine sub1

以及设置通用机器所需的东西:

! funarray.f90
module funmod
   use ISO_FORTRAN_ENV, only: wp => REAL64
   implicit none
   private
   type, public :: Tfun
      contains
      procedure, NOPASS :: R => fun
   end type Tfun
   contains
      function fun(i) bind(C)
         integer, value :: i
         real(wp) fun
         fun = i ! or whatever
      end function fun
end module funmod

module arraymod
   use ISO_FORTRAN_ENV, only: wp => REAL64
   implicit none
   private
   integer, parameter :: N = 10
   type, public :: Tarray
      real(wp) R(N)
   end type Tarray
end module arraymod

module genfun
   use funmod, only: T => Tfun
   implicit none
   private
   public sub
   interface sub
      module procedure sub1
   end interface sub
   contains
include 'sub.i90'
end module genfun

module genarray
   use arraymod, only: T => Tarray
   implicit none
   private
   public sub
   interface sub
      module procedure sub1
   end interface sub
   contains
include 'sub.i90'
end module genarray

module gencombine
   use genfun
   use genarray
   implicit none
end module gencombine

program gentest
   use funmod
   use arraymod
   use gencombine
   implicit none
   type(Tfun) T1
   type(Tarray) T2
   integer i
   do i = 1, size(T2%R)
      T2%R(i) = T1%R(i)
   end do
   call sub(T1,3,'Invoked for function')
   call sub(T2,4,'Invoked for array')
end program gentest

使用 ifort 输出:

Invoked for function
3.000000000000000
Invoked for array
4.000000000000000

【讨论】:

  • 关于bind(c),为什么必须使用它来传递值,如果real(rglu)integer(igul) 不是C 互操作的呢?
  • @francescalus 只有bind(c) 才能获得真正的类 C 传递值。也许在这种函数指针的情况下,当编译器不能做通常的优化机制时,它会有所作为。通常不会。 32 位 x86 和 64 位 x86 调用约定之间的差异可能会有所不同。
  • 不确定其他解决方案,我认为运行时切换是必要的。函数和数组之间的编译时切换是微不足道的,并且不需要派生类型。
  • @francescalus Intel Fortran 有点破坏了 Fortran 的值传递,BIND(C)!DEC$ ATTRIBUTES VALUE 的一些用法除外。使用 gfortran BIND(C) 在这种情况下无需强制执行真正的按值传递。配套处理器可能是 Fortran 编译器本身,在这种情况下,不存在数字类型的互操作性问题。无论如何,如有必要,可以设置rglu = C_DOUBLEigul=C_INTigul=C_INT64_T
  • @VladimirF 运行时切换即使没有派生类型也是可能的,我确实对在发布后将其放在那里有些疑虑,但为了捍卫我的方法,我可以设想派生类型可能导致的实例更快的代码。如果数组hR 的形状(他们必须在这个站点上启用MathJax;没有它,cmets 看起来很原始)在编译时是已知的,但数组或函数是运行时决定的呢?那么 'hR' 必须是 ALLOCATABLE 但对于派生类型,编译器仍然可以硬连线其形状,以获得可能更高效的代码。
【解决方案2】:

用同一个指针指向一个函数和一个数组是完全不可能的。即使在 C 语言中,数据指针 (void *) 和函数指针也是不同的。即使在 CPU 的硬件中,数据地址和代码地址也可以不同地实现。

所以是的,您使用的包装“虚拟”函数似乎是我可以看到使用相同指针的唯一方法。

【讨论】:

  • 我明白,没有这种可能性。这个问题的标题相当挑衅。我只是认为除了我之外的每个人都有一些显而易见的解决方案。
  • 那么请问你真正想问的问题。这就是我们通常在这个网站上所做的。
猜你喜欢
  • 1970-01-01
  • 2016-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多