我有几个4维数组,每个都有不同的大小:
array_one(1:2,1:xm,1:ym,1:zm)
其中 current_step = 1
和 previous_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 回答
复制方法
我使用8种不同的方法在我的系统上运行了一个简单的基准来复制数组 . 我测试了两种基本的复制形式:
和
对于其中的每一个,我还使用
t
索引作为最后一个数组索引进行了测试,例如:和
最后,我按照连续的方式和openmp指令测试了这4个副本中的每一个,例如
对于
do
循环副本和对于数组切片副本 .
对于尺寸为100x100x100x2至1000x1000x1000x2的每个阵列,每个副本执行100次,增量为100(对于所有测试的阵列,ni = nj = nk) .
编译器和编译标志
我用gfortran 4.9.1进行了测试,并编译了我的测试用例
我的CPU是intel i7 990x(6核,启用HT),
native
将以芯片支持的最高指令集为目标 . OpenMP将产生12个线程 .操作系统是Linux 3.12.13 .
结果
每个拷贝的平均时间在y轴上,阵列尺寸在x轴上(例如500是500x500x500x2或2x500x500x500阵列) . 红线是do循环复制(虚线是最后的
t
索引的变化) . 绿线是阵列切片副本(虚线是最后的t
索引的变化) . 对于两个串行副本,首先使用t
索引的变体更快(我没有调查原因)并且数组符号复制比循环更快 . 蓝线是首先使用t
索引的openmp副本 . 黑色线条是最后一个t
索引的openmp副本 . 并行do和并行工作共享结构的性能是等效的 .讨论
使用典型的编译标志在您自己的系统上运行您自己的基准测试 . 这里的结果将特定于我的系统,包括优化标志,SIMD指令和带有12个线程的OpenMP . 对于具有较少内核的系统和具有较少或较大指令集的CPU(例如,具有AVX2的CPU应该执行得更好),这将有所不同 . 这些结果还受缓存局部性,RAM和总线速度以及我的OS调度程序如何处理超线程的影响 .
对于我在我的系统上的结果,我会使用数组切片表示法来进行串行拷贝,为了获得最佳性能,我会使用OpenMP .
简而言之,当一个程序发出一个内存读取操作时,比如
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)
,如果要循环遍历数组,请执行以下操作:此外,执行
A(:)=B(:)
和等效的do循环之间的区别在于A(:)=B(:)
等同于forall
语句:更多在这里http://en.wikipedia.org/wiki/Fortran_95_language_features#The_FORALL_Statement_and_Construct