派生类型访问时间与数组访问时间

Posted

技术标签:

【中文标题】派生类型访问时间与数组访问时间【英文标题】:Derived type access time vs arrays access time 【发布时间】:2016-06-08 17:33:42 【问题描述】:

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

   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 倍)

我在编译和 ifort 英特尔编译器期间仅使用 -O2 选项。在上面的代码 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( :)%Gxa。仅更改几行代码可能会更改可见的执行时间。 我已经用 gfortran 检查了它,但结果是一样的。这是我正在谈论的完整代码的一部分:modskminv_fast.f90。 很抱歉发布完整的模块,但是当我将代码分成小块时,我无法获得相同的结果。

【问题讨论】:

你能准备一个我们可以测试的简化例子吗?我们如何从这些不完整的sn-ps代码中得出明确的答案!我们甚至不知道你的变量是什么类型,什么是函数调用,什么是数组。 但请注意,您正在遍历最后一个索引。 你好,谢谢你的评论,我会试着做一个例子,放在我的github上。我希望,我会设法使它与 gfortran 一起工作。代码 #1 或 #2 中的最后一个索引是什么意思?我认为在第二个代码中迭代是可以的,我在互联网上跟着一些教程。但是,我检查了这在我的案例中确实发挥了最重要的作用。 这就是为什么我只将完整代码的一部分放在一个模块中,总共有 500 行,我在 main.f90 中使用它,它有额外的 100 行... 好的,那里都是数组。我将尝试检查我是否能够制作一个非常简短的示例,这将完成部分工作。 【参考方案1】:

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

complex(16)

而不是

complex*16 or complex(kind=8)

抱歉打扰了。现在,它就像一个魅力:)

【讨论】:

以上是关于派生类型访问时间与数组访问时间的主要内容,如果未能解决你的问题,请参考以下文章

派生类型必须与基本类型的安全可访问性匹配,或者在非常基本的情况下不易访问

从基类类型的向量访问派生类方法

Fortran 派生类型包含可从 C 访问的指针

无法从派生类型列表访问扩展方法

在 Fortran 中多次循环后访问派生数据类型

如何从基对象的派生类访问函数