fortran运算符重载:函数或子例程

时间:2015-01-30 17:32:22

标签: function fortran operator-overloading gfortran subroutine

我最近将我的.f90代码更新为.f03,我期待看到加速,因为我的旧版本涉及许多分配和解除分配(7个3D阵列 - 45x45x45)在do循环内的每次迭代(总共4000个) 。使用派生类型,我在模拟开始时分配这些数组,并在结束时释放它们。我以为我会看到加速,但它实际上运行速度明显较慢(30分钟而不是23分钟)。

我运行了一个分析器,看起来加/减/乘/除运算符需要相对较长的时间。除了标准变化的变化之外,就我所知,运营商是唯一的区别。我想知道这是否是因为函数在每次操作期间都返回了字段数量的新副本。

所以这是我的问题:如果我将函数更改为子例程以使这些字段通过引用传递(我认为?),它可能运行得更快吗?此外,如果这更快,更优选,那么为什么所有这些示例都显示运算符重载的函数而不是使用子例程?我觉得我错过了什么。

带有运算符重载的函数的引用:

http://www.mathcs.emory.edu/~cheung/Courses/561/Syllabus/6-Fortran/operators.html

http://research.physics.illinois.edu/ElectronicStructure/498-s97/comp_info/overload.html

https://web.stanford.edu/class/me200c/tutorial_90/13_extra.html

https://www.ibm.com/developerworks/community/blogs/b10932b4-0edd-4e61-89f2-6e478ccba9aa/entry/object_oriented_fortran_does_fortran_support_operator_overloading55?lang=en

以下是我的一位经营者的例子:

    function vectorVectorDivide(f,g) result(q)
      implicit none
      type(vectorField),intent(in) :: f,g
      type(vectorField) :: q
      q%x = f%x / g%x; q%y = f%y / g%y; q%z = f%z / g%z
      q%sx = f%sx; q%sy = f%sy; q%sz = f%sz
    end function

非常感谢任何帮助或信息!

1 个答案:

答案 0 :(得分:7)

这里有两个问题:

  1. 在某些情况下,使用子程序方法比函数方法可以获得更好的性能吗?
  2. 为什么,如果表现更差,我想使用一个功能吗?
  3. 关于第一个问题,一个重要的事情是,你可能最好自己测试一些东西:这里有很多具体的方面。

    然而,我很快就敲了一些可能引导你的事情。

    module test
    
      implicit none
    
      type t1
         real, allocatable :: x(:)
      end type t1
    
    contains
    
      function div_fun(f,g) result(q)
        type(t1), intent(in) :: f, g
        type(t1) q
        q%x = f%x/g%x
      end function div_fun
    
      subroutine div_sub1(f, g, q)
        type(t1), intent(in) :: f, g
        type(t1), intent(out) :: q
        q%x = f%x/g%x
      end subroutine div_sub1
    
      subroutine div_sub2(f, g, q)
        type(t1), intent(in) :: f, g
        type(t1), intent(inout) :: q
        q%x(:) = f%x/g%x
      end subroutine div_sub2
    
    end module test
    

    有了这个,我观察到有时在使用函数和子程序之间没有显着差异,有时会有。也就是说,它取决于编译器,标志等。

    但是,重要的是要注意发生了什么。

    对于函数,结果需要分配,对于子例程div_sub1intent(out)参数需要分配。 [分配功能结果会增加内容 - 请参阅后面的内容。]

    div_sub2中重新使用分配("结果"参数为intent(inout)),我们使用q%x(:)禁止自动重新分配。后一部分很重要:编译器经常会花费额外的费用来检查是否需要调整大小。可以通过将qdiv_sub1的意图更改为inout来测试后一部分。

    [请注意,这种div_sub2方法假设尺寸不变;这似乎得到了你的文字的支持。]

    总结第一个问题:检查一下你自己,但想知道你是否只是"隐藏"通过使用派生类型而不是删除它们进行分配。使用参数化派生类型可能会得到非常不同的答案。

    提出第二个问题,为什么常用函数?您会注意到我已经查看了非常具体的案例:

    q = div_fun(f,g)
    call div_sub2(f,g,q)  ! Could be much faster
    

    从问题文本和链接(以及您之前提出的问题)我假设您有一些超出/运算符的内容

    interface operator (/)
      module procedure div_fun
    end interface
    

    允许

    q = f/g               ! Could be slower, but looks good.
    call div_sub2(f,g,q)
    

    正如我们注意到要用作二元运算符(参见Fortran 2008 7.1.5,7.1.6),该过程必须是一个函数。回复您对此答案的先前修订版的评论

      

    不是div_sub1和div_sub2二元运算符就像div_fun一样?

    答案是" no",至少就Fortran定义为二元运算符而言(链接如上)。 [另外,div_fun本身并不是二元运算符,它是构成运算的函数和通用接口的组合。]

    使函数方法具有吸引力是二元运算可以是表达式的一部分:

    q = q + alpha*(f/g)                ! Very neat
    call div_sub2(f,g,temp1)
    call mult_sub(alpha, temp1, temp2)
    call add_sub(q, temp2, temp3)
    call assign_sub(q, temp3)
    

    使用子程序可能会有点乱。通过处理"就地"上面的例子可以稍微整理一下。方面(或专家子程序),但这让我想到了最后一点。因为函数结果在以后使用(包括赋值)之前完全被评估,所以我们有像

    这样的情况
    f = f/g  ! or f=div_fun(f,g)
    call div_sub2(f,g,f) ! Beware aliasing
    

    总结第二个问题:表现并不是一切。

    [最后,如果您的意思是使用.f90.f03文件后缀来表示/管理标准合规性,那么您可能希望了解人们对此的看法。]