我想知道“%”如何在派生类型的数组上起作用。
我编写了以下程序,其中“+”运算符已被重载:
module my_mod
implicit none
type :: my_type
real :: r1 , r2
integer :: i1, i2
end type my_type
interface operator(+)
module procedure add_my_type_vec, add_my_type
end interface
contains
function add_my_type_vec(a,b) result(res)
type(my_type), dimension(:),intent(in) :: a, b
type(my_type), dimension(size(a)) :: res
res%r1 = a%r1 + b%r1
res%r2 = a%r2 + b%r2
res%i1 = a%i1 + b%i1
res%i2 = a%i2 + b%i2
end function add_my_type_vec
function add_my_type(a,b) result(res)
type(my_type), intent(in) :: a, b
type(my_type) :: res
res%r1 = a%r1 + b%r1
res%r2 = a%r2 + b%r2
res%i1 = a%i1 + b%i1
res%i2 = a%i2 + b%i2
end function add_my_type
end module my_mod
program my_pgm
use my_mod
implicit none
type(my_type),allocatable, dimension(:) :: my_vec1, my_vec2, my_vec3
write(*,*) "Sum on type : ", my_type(1.0, 2.0, 1, 2) + my_type(1.0, 2.0, 1, 2)
allocate(my_vec1(1000), my_vec2(1000), my_vec3(1000))
my_vec1 = my_type(1.0, 2.0, 1, 2)
my_vec2 = my_type(1.0, 2.0, 1, 2)
my_vec3 = my_vec1 + my_vec2
write(*,*) "Sum on vec of type : ", my_vec3(123)
end program my_pgm
在函数中add_my_type_vec
,它是如何%
工作的?例如,在指令中res%r1 = a%r1 + b%r1
,是否创建(即内存复制)两个仅包含a%r1
和 的实数数组b%r1
,然后对这些数组进行求和,最后对 进行赋值res%r1
?我想这应该更复杂一些。
编译器通常可以随时创建数组临时变量。1在 某些情况下,例如您在这里的许多帖子中看到的那样,临时副本实际上是必需的,或者非常可取。 例如,另一个答案列举了一些情况。
作为一个实际的例子,这是一个可以详细阐述思考的案例,而不是给出严格的答案。
在此处的整个代码中,有很多机会可以在周围创建临时数组
add_my_type_vec
,例如:a
,b
可以被复制a%r1
、b%r1
等)可以分别复制a%r1+b%r1
等)可以进入临时位置这里不需要任何课程的副本,或者特别有益。
虚拟参数
a
和b
是假定形状,并且编译器知道它们(简单)是连续的。数组
a%r1
(等)不是连续的,但具有恒定的步幅:编译器可以轻松处理元素的步幅。的左侧
res%r1 = a%r1 + b%r1
不会以任何方式(尤其是不是复杂的方式)出现在右侧:右侧的评估不会影响左侧,因此在分配之前不需要副本来保存评估。同样,在函数结果的分配中,所涉及的任何变量之间不存在相互作用。
无需在任何地方解决最终确定的要求。
本质上,编译器所要做的就是迭代的元素
my_vec3
并直接分配和的元素的成对my_vec1
和my_vec2
,以相当无聊的方式遍历这些数组。如果编译器认为有必要,或者为了更加安全,它可以决定执行任何复制操作。通常,你可以让编译器在编译时或运行时告诉你它何时进行复制,或者何时设置机制来启用复制决策。
唯一需要担心组件(使用
%
)的原因是引用的数组不连续。Fortran 能够很好地处理非连续数组,而无需复制。对“复杂”数组和组件“复杂”使用的限制,使得处理这些常见情况变得非常枯燥(对于编译器编写者而言)。1编译器必须确保对临时变量的影响也影响与其关联的实体。例如,如果存在一个伪参数的临时副本,则实际参数必须反映任何(适当的)更改。Fortran 的别名规则是对程序的限制,这使得编译器在确定何时需要在诸如拷贝入/拷贝出等技术中显示影响时更加容易。
在数组操作
res(:)%r1 = a(:)%r1 + b(:)%r1
(例如中的操作)中add_my_type_vec()
,我希望任何合适的优化编译器都不会创建临时数组。不过,就内存访问而言,这种方法可能并非最佳:
a(:)%r1
它是一个步幅为 4 的非连续数组(因为派生类型中有 4 个数字存储单元),而且据我所知,矢量指令在步幅为 4 的数组上效率较低。此外,通过顺序执行 4 个数组操作,在缓存行被清除并稍后重新加载之前,只会使用缓存行内容的 25%。另一方面,通过对元素(派生类型数组)进行求和,可以更好地利用缓存,但无法利用矢量指令。
最后,只有基准测试才能告诉您对于您正在使用的给定机器/编译器组合来说最有效的方法是什么。
请注意,您实际上不需要在这里编写两个单独的例程。您可以编写一个
elemental
例程来处理标量和数组的加法(不过,编译器在后台会做什么并不明显)。