首页 文章

在Fortran中有效地移动多维数组

提问于
浏览 1942
2

我有几个4维数组,每个都有不同的大小:

array_one(1:2,1:xm,1:ym,1:zm)

其中 current_step = 1previous_step = 2 .

在一个长循环中,有许多其他操作,我需要将current_step值移到previous_step,如:

array_one(previous_step,:,:,:) = array_one(current_step,:,:,:)

我知道我可以在DO循环中做到这一点,但也许它不是最有效的方法 . 由于我至少有24个这样的阵列,每个阵列都有不同的大小(即xm,ym,zm),所以我需要为它们中的每一个运行单独的DO循环,这可能使它变慢 .

我以下列方式失败了:

array_one(previous_step,:,:,:) = array_one(current_step,:,:,:)

这种转变的有效方式是什么?

2 回答

  • 1

    复制方法

    我使用8种不同的方法在我的系统上运行了一个简单的基准来复制数组 . 我测试了两种基本的复制形式:

    do k=1,nx
    do j=1,nx
    do i=1,nx
       array(2,i,j,k) = array(1,i,j,k)
    end do
    end do
    end do
    

    array(2,:,:,:) = array(1,:,:,:)
    

    对于其中的每一个,我还使用 t 索引作为最后一个数组索引进行了测试,例如:

    array(i,j,k,2) = array(i,j,k,1)
    

    array(:,:,:,2) = array(:,:,:,1)
    

    最后,我按照连续的方式和openmp指令测试了这4个副本中的每一个,例如

    !$omp parallel do shared(array) private(i,j,k)
    ...
    !$omp end parallel do
    

    对于 do 循环副本和

    !$omp parallel workshare shared(array)
    ...
    !$omp end parallel workshare
    

    对于数组切片副本 .

    对于尺寸为100x100x100x2至1000x1000x1000x2的每个阵列,每个副本执行100次,增量为100(对于所有测试的阵列,ni = nj = nk) .

    编译器和编译标志

    我用gfortran 4.9.1进行了测试,并编译了我的测试用例

    gfortran -march=native -fopenmp -O3 -o arraycopy arraycopy.f90
    

    我的CPU是intel i7 990x(6核,启用HT), native 将以芯片支持的最高指令集为目标 . OpenMP将产生12个线程 .

    操作系统是Linux 3.12.13 .

    结果

    Copy results

    每个拷贝的平均时间在y轴上,阵列尺寸在x轴上(例如500是500x500x500x2或2x500x500x500阵列) . 红线是do循环复制(虚线是最后的 t 索引的变化) . 绿线是阵列切片副本(虚线是最后的 t 索引的变化) . 对于两个串行副本,首先使用 t 索引的变体更快(我没有调查原因)并且数组符号复制比循环更快 . 蓝线是首先使用 t 索引的openmp副本 . 黑色线条是最后一个 t 索引的openmp副本 . 并行do和并行工作共享结构的性能是等效的 .

    讨论

    使用典型的编译标志在您自己的系统上运行您自己的基准测试 . 这里的结果将特定于我的系统,包括优化标志,SIMD指令和带有12个线程的OpenMP . 对于具有较少内核的系统和具有较少或较大指令集的CPU(例如,具有AVX2的CPU应该执行得更好),这将有所不同 . 这些结果还受缓存局部性,RAM和总线速度以及我的OS调度程序如何处理超线程的影响 .

    对于我在我的系统上的结果,我会使用数组切片表示法来进行串行拷贝,为了获得最佳性能,我会使用OpenMP .

  • 1

    简而言之,当一个程序发出一个内存读取操作时,比如 A(i) ,它不仅会读取 A(i) ,而是会读取类似 A(i-2), A(i-1), A(i), A(i+1), A(i+2) 的内容 . 然后将这些值存储在CPU缓存中,这是一个更快的内存 . 也就是说,CPU将读取一块内存并将其放入缓存中供以后使用 . 此优化基于以下事实:您的下一个操作很可能会使用其中一些周围值 . 如果那个's the case, the CPU won't需要再次获取内存,这是一个非常昂贵的操作(比浮点操作贵100倍),而只需要在缓存中查找值 . 这称为数据局部性 .

    在Fortran中,多维数组以列主顺序存储 . 例如,假设您有以下2x2矩阵:

    A(1,1)=a11, A(1,2)=a12, A(2,1)=a21, A(2,2)=a22 .

    矩阵 A(1:2,1:2) 按以下顺序线性存储在内存中: a11, a21, a12, a22 (相反,在C语言的行主要顺序中,顺序为 a11, a12, a21, a22 ) . 您可以推断出更高维度的订单 .

    简而言之,Fortran数组从左到右线性存储在内存中 . 如果要利用数据局部性,则需要从左到右遍历数组 .

    Short answer: 我认为您应该将结构更改为 (1:xm,1:ym,1:zm,1:2) ,如果要循环遍历数组,请执行以下操作:

    do h = 1, 2
      do i = 1, zm
        do j = 1, ym
          do k = 1, xm
            A[k,j,i,h] = *...something...*
          end do
        end do
      end do
    end do
    

    此外,执行 A(:)=B(:) 和等效的do循环之间的区别在于 A(:)=B(:) 等同于 forall 语句:

    forall(i = 1:n)
      A(i) = B(i)
    end forall
    

    更多在这里http://en.wikipedia.org/wiki/Fortran_95_language_features#The_FORALL_Statement_and_Construct

相关问题