3

我有一个与多维数组或派生类型的访问时间有关的问题。我写了一个运行良好的算法。但是,该算法的主要部分是%通过不同的类型引用一些数据。例如,这是我的代码中最昂贵的循环:

   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 时间:

  1. 0.1s 为派生类型方法(第一个代码)
  2. 数组方法需要 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。很抱歉发布完整的模块,但是当我将代码分成小块时,我无法获得相同的结果。

4

1 回答 1

0

问题显然在我这边。只是偶然我将所有复杂数组声明为:

complex(16)

代替

complex*16 or complex(kind=8)

对不起,打扰你。现在,它就像一个魅力:)

于 2016-02-29T15:26:00.267 回答