Sun Studio 12:Fortran 编程指南

10.2 自动并行化

使用 -autopar 选项,f95 编译器会自动查找可以有效并行化的 DO 循环。然后对这些循环进行转换,将其迭代均匀分布在可用的处理器上。编译器会生成实现这一目标所需的线程调用。

10.2.1 循环并行化

编译器的依赖性分析会将 DO 循环转换成可并行化的任务。编译器可能会重构循环,分离出将要串行运行的不可并行化部分。然后将工作均匀分布在可用的处理器上。每个处理器执行不同的迭代块。

例如,对于四个 CPU 和具有 1000 次迭代的并行化循环,每个线程将执行含有 250 次迭代的程序块。

处理器 1 执行迭代 

至 

250 

处理器 2 执行迭代 

251 

至 

500 

处理器 3 执行迭代 

501 

至 

750 

处理器 4 执行迭代 

751 

至 

1000 

只有不依赖于计算执行顺序的循环才能成功进行并行化。编译器的依赖性分析拒绝对那些具有内在数据依赖性的循环进行并行化。如果不能完全确定循环中的数据流,编译器会保守行事,不进行并行化。另外,如果能确定性能增益抵不上总开销,编译器也不会对循环进行并行化。

注意:编译器总是选择对使用静态循环调度的循环进行并行化-即将循环中的工作拆分到多个等效的迭代块中。其他调度方案可以用本章后面所述的显式并行化指令来指定。

10.2.2 数组、标量和纯标量

自动并行化角度看,需要一些新定义:

示例:数组/标量:


      dimension a(10)
      real m(100,10), s, u, x, z
      equivalence ( u, z )
      pointer ( px, x )
      s = 0.0
      ...

ma 都是数组变量;s 是纯标量。变量 uxzpx 是标量变量,但不是标量。

10.2.3 自动并行化标准

不具有任何交叉迭代数据依赖性的 DO 循环由 -autopar 自动并行化。自动并行化的一般标准是:

10.2.3.1 直观依赖性

编译器可以自动消除显示的引用以在循环中创建数据依赖性。这种转换有许多,其中之一会利用某些数组的专用版本。通常,如果编译器能确定这种数组在原始循环中只是作为临时存储使用,它便会这样做。

示例:使用 -autopar,通过专用数组消除了依赖性:


      parameter (n=1000)
      real a(n), b(n), c(n,n)
      do i = 1, 1000             <--Parallelized
        do k = 1, n
          a(k) = b(k) + 2.0
        end do
        do j = 1, n-1
          c(i,j) = a(j+1) + 2.3
        end do
      end do
      end

在此例中,外层循环被并行化,并在独立的处理器上运行。虽然内层循环对数组 a 的引用看起来会导致数据依赖性,但编译器会生成数组的临时专用副本,使外层循环迭代变得独立。

10.2.3.2 自动并行化抑制因素

自动并行化过程中,如果以下条件下成立,编译器不会对循环进行并行化:

10.2.3.3 嵌套循环

在多线程多处理器环境中,对循环嵌套中最外层循环(而不是最内层循环)进行并行化最有效。由于并行处理通常涉及相对较大的循环开销,所以并行化最外层循环会最大程度地减少开销并增加每个线程完成的工作量。在自动并行化情况下,编译器从最外层嵌套循环开始进行循环分析,然后继续向内进行直至找到可并行化的循环。一旦嵌套中的某个循环被并行化,便会略过该并行循环内所包含的循环。

10.2.4 具有约简操作的自动并行化

将一个数组转换为一个标量的计算称为约简操作。典型的约简操作有矢量元素的求和或求积。违反循环中计算标准的约简操作不能在各次迭代间以累积方式改变标量变量。

示例:向量元素的约简求和:


      s = 0.0
      do i = 1, 1000
        s = s + v(i)
      end do
      t(k) = s

但是,对于某些操作,如果约简是阻止并行化的唯一因素,仍然可以对循环进行并行化。常见约简操作出现频率很高,因而编译器能够将其视为特例进行并行化。

自动并行化分析不包括约简操作的识别,除非随 -autopar-parallel 一同指定了 -reduction 编译器选项。

如果某一可并行化的循环包含表 10–2 中列出的某一项约简操作,则当指定了 -reduction 时,编译器将会对其进行并行化。

10.2.4.1 识别的约简操作

下表列出了编译器识别的约简操作。

表 10–2 识别的约简操作

数学运算 

Fortran 语句模板 

求和 

s = s + v(i)

求积 

s = s * v(i)

点积 

s = s + v(i) * u(i)

最小值 

s = amin( s, v(i))

最大值 

s = amax( s, v(i))

OR

do i = 1, n

b = b .or. v(i)

end do

AND

b = .true.

do i = 1, n

b = b .and. v(i)

end do

非零元素计数 

k = 0

do i = 1, n

if(v(i).ne.0) k = k + 1

end do

识别所有形式的 MINMAX 函数。

10.2.4.2 数值准确性和约简操作

由于以下原因,浮点型数字的求和或求积约简操作可能不准确:

在一些情况下,该误差是不能接受的。

示例:舍入,求介于 –1 和 +1 之间的 100,000 个随机数之和:


demo% cat t4.f
      parameter ( n = 100000 )
      double precision d_lcrans, lb / -1.0 /, s, ub / +1.0 /, v(n)
      s = d_lcrans ( v, n, lb, ub ) ! Get n random nos. between -1 and +1
      s = 0.0
      do i = 1, n
        s = s + v(i)
      end do
      write(*, ’(" s = ", e21.15)’) s
      end
demo% f95 -O4 -autopar -reduction t4.f

结果会随着处理器的个数而变化。下表展示了介于 – 1 和 +1 之间的 100,000 个随机数之和。

处理器数 

输出 

s = 0.568582080884714E+02

s = 0.568582080884722E+02

s = 0.568582080884721E+02

s = 0.568582080884724E+02

在这种情况下,对于随机开始的数据而言,10-14 阶的舍入误差是可以接受的。有关更多信息,请参见 Sun《数值计算指南》。