Fortran 运算符重载:函数或子例程
fortran operator overloading: function or subroutine
我最近将我的 .f90 代码更新为 .f03,我期待看到加速,因为我的旧版本在 do 循环(4000 in全部的)。使用派生类型,我在模拟开始时分配这些数组并在结束时取消分配它们。我以为我会看到加速,但实际上 运行ning 显着变慢(30 分钟而不是 23 分钟)。
我 运行 一个分析器,看起来 add/subtract/multiply/divide 运算符花费了相对较长的时间。除了标准变化的变化外,据我所知,运营商是唯一的区别。我想知道这是否是因为函数在每次操作期间都返回字段数量的新副本。
所以这是我的问题:如果我将函数更改为子例程以便这些字段通过引用传递(我认为?),它会 运行 更快吗?此外,如果这更快、更受欢迎,那么为什么所有这些示例都显示用于运算符重载的函数而不是使用子例程?我觉得我错过了什么。
具有运算符重载的函数参考:
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
这是我的一位操作员的示例:
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
非常感谢任何帮助或信息!
这里有两个问题:
- 在某些情况下,与函数方法相比,使用子例程方法可以获得更好的性能吗?
- 为什么,如果性能更差,我要使用函数吗?
关于第一个问题要说的重要一点是,您最好自己测试一下:这有很多具体方面。
但是,我很快敲了一些东西,可以指导你。
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_sub1
,intent(out)
参数需要分配。 [分配函数结果会增加东西 - 稍后见。]
在 div_sub2
中重新使用分配("result" 参数是 intent(inout)
)并且我们使用 q%x(:)
抑制自动重新分配。后一部分很重要:编译器经常会因为检查是否需要调整大小而产生开销。可以通过将 div_sub1
中的 q
的意图更改为 inout
.
来测试后一部分
[请注意,此 div_sub2
方法假设尺寸不变;这似乎得到了你的文字的支持。]
第一个问题的结论:自己检查一下,但想知道您是否只是 "hiding" 通过使用派生类型而不是删除派生类型进行分配。使用参数化派生类型,您可能会得到截然不同的答案。
来到第二个问题,为什么常用函数?您会注意到我研究了非常具体的案例:
q = div_fun(f,g)
call div_sub2(f,g,q) ! Could be much faster
根据问题文本和 links(以及您之前提出的问题),我假设您有一些东西使 /
运算符
过载
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),该过程必须是一个函数。回应您对此答案先前修订的评论
aren't the div_sub1 and div_sub2 binary operators just like the div_fun?
答案是 "no",至少就 Fortran 定义的二元运算符而言是这样(link,如上所述)。 [此外,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)
使用子例程可能会有点混乱。上面的例子可以通过处理 "in-place" 个方面(或专门的子程序)稍微整理一下,但这让我到了最后一点。因为函数结果在以后使用(包括赋值)之前完全被评估,所以我们有这样的情况
f = f/g ! or f=div_fun(f,g)
call div_sub2(f,g,f) ! Beware aliasing
第二个问题的结论:性能不是一切。
[最后,如果您的意思是您正在使用 .f90
和 .f03
文件后缀以符合 denote/govern 标准,那么您可能想看看人们对此有何看法。]
我最近将我的 .f90 代码更新为 .f03,我期待看到加速,因为我的旧版本在 do 循环(4000 in全部的)。使用派生类型,我在模拟开始时分配这些数组并在结束时取消分配它们。我以为我会看到加速,但实际上 运行ning 显着变慢(30 分钟而不是 23 分钟)。
我 运行 一个分析器,看起来 add/subtract/multiply/divide 运算符花费了相对较长的时间。除了标准变化的变化外,据我所知,运营商是唯一的区别。我想知道这是否是因为函数在每次操作期间都返回字段数量的新副本。
所以这是我的问题:如果我将函数更改为子例程以便这些字段通过引用传递(我认为?),它会 运行 更快吗?此外,如果这更快、更受欢迎,那么为什么所有这些示例都显示用于运算符重载的函数而不是使用子例程?我觉得我错过了什么。
具有运算符重载的函数参考:
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
这是我的一位操作员的示例:
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
非常感谢任何帮助或信息!
这里有两个问题:
- 在某些情况下,与函数方法相比,使用子例程方法可以获得更好的性能吗?
- 为什么,如果性能更差,我要使用函数吗?
关于第一个问题要说的重要一点是,您最好自己测试一下:这有很多具体方面。
但是,我很快敲了一些东西,可以指导你。
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_sub1
,intent(out)
参数需要分配。 [分配函数结果会增加东西 - 稍后见。]
在 div_sub2
中重新使用分配("result" 参数是 intent(inout)
)并且我们使用 q%x(:)
抑制自动重新分配。后一部分很重要:编译器经常会因为检查是否需要调整大小而产生开销。可以通过将 div_sub1
中的 q
的意图更改为 inout
.
[请注意,此 div_sub2
方法假设尺寸不变;这似乎得到了你的文字的支持。]
第一个问题的结论:自己检查一下,但想知道您是否只是 "hiding" 通过使用派生类型而不是删除派生类型进行分配。使用参数化派生类型,您可能会得到截然不同的答案。
来到第二个问题,为什么常用函数?您会注意到我研究了非常具体的案例:
q = div_fun(f,g)
call div_sub2(f,g,q) ! Could be much faster
根据问题文本和 links(以及您之前提出的问题),我假设您有一些东西使 /
运算符
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),该过程必须是一个函数。回应您对此答案先前修订的评论
aren't the div_sub1 and div_sub2 binary operators just like the div_fun?
答案是 "no",至少就 Fortran 定义的二元运算符而言是这样(link,如上所述)。 [此外,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)
使用子例程可能会有点混乱。上面的例子可以通过处理 "in-place" 个方面(或专门的子程序)稍微整理一下,但这让我到了最后一点。因为函数结果在以后使用(包括赋值)之前完全被评估,所以我们有这样的情况
f = f/g ! or f=div_fun(f,g)
call div_sub2(f,g,f) ! Beware aliasing
第二个问题的结论:性能不是一切。
[最后,如果您的意思是您正在使用 .f90
和 .f03
文件后缀以符合 denote/govern 标准,那么您可能想看看人们对此有何看法。]