我有一个与多维数组或派生类型的访问时间有关的问题。我写了一个运行良好的算法。但是,该算法的主要部分是%
通过不同的类型引用一些数据。例如,这是我的代码中最昂贵的循环:
do c_elem = 1 , no_elems
call tmp_element%init_element(no_fl)
tmp_element%Gij(no_fl,1) = e_Gxa(c_elem)
tmp_element%Gij(no_fl,2) = e_Gax(c_elem)
GAX = tmp_element%Gij(no_fl,1)/GAA
GXA = tmp_element%Gij(no_fl,2)/GAA
do k = 1 , no_fl-1
s1 = this%flNew%Iij(k)
i1 = this%nodes(s1)%fl_id
tmp_element%Gij(k,1) = this%elOld(c_elem)%Gij(i1,1) + GAX*this%flNew%Gij(no_fl,k)
tmp_element%Gij(k,2) = this%elOld(c_elem)%Gij(i1,2) + this%flNew%Gij(k,no_fl)*GXA
enddo
call this%elOld(c_elem)%init_element(no_fl)
this%elOld(c_elem)%Gij = tmp_element%Gij
enddo
正如您所看到的,大多数时候我通过访问一些变量,%
并且大多数数组都有“可分配”的,复数或整数。我读过有时使用派生类型可能会导致计算速度变慢,因此我决定重写我的算法以直接在没有类型的数组上工作。现在只使用数组的相同循环如下所示:
do c_elem = 1 , no_elems
nGij1(no_fl,c_elem) = e_Gxa(c_elem)
nGij2(no_fl,c_elem) = e_Gax(c_elem)
GAX = e_Gxa(c_elem)/GAA
GXA = e_Gax(c_elem)/GAA
do k = 1 , no_fl-1
i1 = fl_id(k)
nGij1(k,c_elem) = oGij1(i1,c_elem) + GAX*fl_new_Gij(no_fl,k)
nGij2(k,c_elem) = oGij2(i1,c_elem) + fl_new_Gij(k,no_fl)*GXA
enddo
enddo
我预计上面的代码会比我使用派生类型的代码运行得更快。所以我检查了时间。以下是这部分代码的 CPU 时间:
- 0.1s 为派生类型方法(第一个代码)
- 数组方法需要 3.2 秒(第二个代码)(慢约 30 倍)
我-O2
在编译期间只使用该选项和 ifort 英特尔编译器。在上面的代码 no_elems >> no_fl 中,大多数数组和类型都很复杂。很明显,两种算法的唯一区别是内存访问时间。
为什么这两种情况的执行时间存在巨大差异?
编辑#1
我试图做一个简短的例子,但是我无法像使用完整代码一样获得类似的结果......两种情况的时间几乎相同——数组稍微快一点。
我可以向您展示对我来说完全陌生的东西:我有一个类型:
type element
complex*16,allocatable :: Gij(:,:)
integer :: no_sites
complex*16 :: Gp,Gxa,Gax
integer :: i,j
contains
procedure,pass(this) :: free_element
procedure,pass(this) :: init_element
procedure,pass(this) :: copy_element
end type element
然后,在一个大循环的某个时刻,我计算如下值:
! derived type way
this%elOld(c_elem)%Gax = GAX
this%elOld(c_elem)%Gxa = GXA
this%elOld(c_elem)%Gp = this%elOld(c_elem)%Gp + GXA*GAX/GAA
! array way to do it
e_Gax(c_elem) = GAX
e_Gxa(c_elem) = GXA
e_Gp (c_elem) = e_Gp (c_elem) + GXA*GAX/GAA
在哪里:
complex(16),allocatable :: e_Gax(:),e_Gxa(:),e_Gp(:)
elOld 是以另一种类型定义的元素数组
type(element),allocatable :: elOld(:),elNew(:)
然后我有我已经向你展示过的循环,但如果我会这样做:
tmp_element%Gij(no_fl,1) = e_Gxa(c_elem)
tmp_element%Gij(no_fl,2) = e_Gax(c_elem)
GAX = e_Gxa(c_elem)/GAA
GXA = e_Gxa(c_elem)/GAA
而不是:
tmp_element%Gij(no_fl,1) = this%elOld(c_elem)%Gxa
tmp_element%Gij(no_fl,2) = this%elOld(c_elem)%Gax
GAX = tmp_element%Gij(no_fl,1)/GAA
GXA = tmp_element%Gij(no_fl,2)/GAA
程序变得明显变慢了...(使用 type(elemen) :: tmp_element),这意味着访问简单数组 e_Gxa(:) 比访问类型数组中的复杂变量 Gxa 慢,即 this%elOld(:)%甲。仅更改几行代码可能会更改可见的执行时间。我已经用 gfortran 检查了它,但结果是一样的。这是我正在谈论的完整代码的一部分:modskminv_fast.f90。很抱歉发布完整的模块,但是当我将代码分成小块时,我无法获得相同的结果。