我继承了一段Fortran代码,因为它的任务是为我们拥有的8核机器并行化。我有两个版本的代码,我正在尝试使用openMP编译器指令来加速它。它适用于一段代码,但不适用于其他代码,我无法弄清楚为什么 - 它们几乎相同! 我使用和不使用openMP标记运行每段代码,第一段显示速度提升,但不是第二块。我希望我能清楚地解释这个......
代码示例1 :(显着改进)
!$OMP PARALLEL DO
DO IN2=1,NN(2)
DO IN1=1,NN(1)
SCATT(IN1,IN2) = DATA((IN2-1)*NN(1)+IN1)/(NN(1)*NN(2))
UADI(IN1,IN2) = SCATT(IN1,IN2)+1.0
ENDDO
ENDDO
!$OMP END PARALLEL DO
代码示例2 :(没有改进)
!$OMP PARALLEL DO
DO IN2=1,NN(2)
DO IN1=1,NN(1)
SCATTREL = DATA(2*((IN2-1)*NN(1)+IN1)-1))/NN(1)*NN(2))
SCATTIMG = DATA(2*((IN2-1)*NN(1)+IN1)))/NN(1)*NN(2))
SCATT(IN1,IN2) = DCOMPLX(SCATREL, SCATIMG)
UADI(IN1,IN2) = SCATT(IN1,IN2)+1.0
ENDDO
ENDDO
!$OMP END PARALLEL DO
我认为这可能是内存ovehead等问题,并尝试过将变量置于shared()和private()子句中的各种组合,但它们会导致分段错误或使其更慢。
我还认为可能是因为我在循环中没有做足够的工作来看到改进,但是因为对我来说没有意义的小循环有所改进。
任何人都可以了解我能做些什么看到第二个真正的速度提升?
代码示例1的速度提升数据:
平均运行时间(对于整个代码而不仅仅是此代码段)
Without openMP tags: 2m 21.321s
With openMP tags: 2m 20.640s
平均运行时间(仅此代码段的配置文件)
Without openMP tags: 6.3s
With openMP tags: 4.75s
代码示例2的速度提升数据:
平均运行时间(对于整个代码而不仅仅是此代码段)
Without openMP tags: 4m 46.659s
With openMP tags: 4m 49.200s
平均运行时间(仅此代码段的配置文件)
Without openMP tags: 15.14s
With openMP tags: 46.63s
答案 0 :(得分:2)
观察到代码并行运行比串行运行慢,这告诉我罪魁祸首很可能是false sharing。
SCATT
数组为shared
,每个线程都会访问其中的一部分,以便读取和写入。您的代码中没有竞争条件,但是写入相同数组的线程(尽管是不同的切片)会使速度变慢。
原因是每个线程在缓存中加载数组SCATT
的一部分,并且每当另一个线程写入SCATT
的那部分时,这使先前存储在缓存中的数据无效。虽然输入数据没有改变,因为没有竞争条件(另一个线程更新了SCATT
的不同切片),处理器得到一个缓存无效的信号,从而重新加载数据(详见上面的链接) )。这会导致高数据传输开销。
解决方案是使每个切片对给定线程都是私有的。在您的情况下,它甚至更简单,因为您根本不需要读取SCATT
。只需替换
SCATT(IN1,IN2) = DCOMPLX(SCATREL, SCATIMG)
UADI(IN1,IN2) = SCATT(IN1,IN2)+1.0
带
SCATT0 = DCOMPLX(SCATREL, SCATIMG)
UADI(IN1,IN2) = SCATT0+1.0
SCATT(IN1,IN2) = SCATT0
其中SCATT0
是private
变量。
为什么在第一个片段中不会发生这种情况?当然,我怀疑编译器可能已经优化了问题。在计算DATA((IN2-1)*NN(1)+IN1)/(NN(1)*NN(2))
时,它很可能将其存储在寄存器中,并在SCATT(IN1,IN2)
中使用此值而不是UADI(IN1,IN2) = SCATT(IN1,IN2)+1.0
。
此外,如果你想加快代码速度,你应该让循环更有效率。并行化的第一个规则是不要这样做!首先优化串行代码。因此,将片段1替换为(您甚至可以在最后一行的workshare
内)
DATA/(NN(1)*NN(2))
!$OMP PARALLEL DO private(temp)
DO IN2=1,NN(2)
temp = (IN2-1)*NN(1)
SCATT(:,IN2) = DATA(temp+1:temp+NN(1))
ENDDO
!$OMP END PARALLEL DO
UADI = SCATT+1.0
您也可以使用代码段2执行类似操作。