【问题标题】:fortran operator overloading: function or subroutinefortran 运算符重载:函数或子例程
【发布时间】:2015-01-30 17:32:21
【问题描述】:

我最近将我的 .f90 代码更新为 .f03,我期待看到加速,因为我的旧版本在 do 循环内的每次迭代中都涉及许多分配和取消分配(7 个 3D 数组--45x45x45)(总共 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

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

【问题讨论】:

  • 是的,可分配数组

标签: function fortran operator-overloading gfortran subroutine


【解决方案1】:

这里有两个问题:

  1. 在某些情况下,与函数方法相比,使用子例程方法可以获得更好的性能吗?
  2. 如果性能更差,我为什么要使用函数?

关于第一个问题要说的重要一点是,您最好自己测试一下:这有很多具体方面。

不过,我很快就找到了一些可以指导你的东西。

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(:) 来抑制自动重新分配。后一部分很重要:编译器经常在检查是否需要调整大小时承受开销。可以通过将div_sub1 中的q 的意图更改为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 一样吗?

答案是“不”,至少就 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 文件后缀来表示/管理标准合规性,那么您可能想看看人们对此有何看法。]

【讨论】:

  • 谢谢,我会尝试测试这些不同的场景,看看性能如何变化。关于为什么这么多来源似乎使用函数方法的任何评论?再次感谢!
  • 我在上一条评论中应该更具体一些。我应该说“关于为什么这么多来源似乎使用函数方法而不是子例程方法的任何评论?”。如果您的答案仍然适用,那么我很困惑,div_sub1 和 div_sub2 二元运算符不就像 div_fun 一样吗?
  • @Charlie 你在这里缺少的是你实际上执行了两个操作而不仅仅是一个 - 你将f除以g并且你还将结果分配给qdiv_fundiv_sub1 做同样的事情,但你将如何超载它们?如果你用/ 重载div_sub1,那么你要求编译器使用一个接受三个参数但只有两个参数的过程来执行f/gfg。你真正想要的是q = div_sub(f,g),那么div_sub 必须是一个合法的函数。
  • @francescalus ,我在 github 上做了一个 repo。这是一个链接:github.com/charliekawczynski/FortranAllocateSpeed
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-20
  • 1970-01-01
  • 2014-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多