Co-Array Fortran 看上去很美 ?
作者: Hao Jiang (Intel) (3 篇文章) 日期: 五月 9, 2011 -- 3:36 下午
TIPS:文章代码一些语法并非F2008的标准写法,请读者注意。
Co-Array Fortran(下面简称 CAF,中文暂称为”Fortran的集合数组阵列扩展“),最初是作为 Fortran 95 一个小小的扩展,由 Numrich 和 Reid 发表于 1998年 ACM Fortran Forum 17卷第2期的 “Co-Array Fortran for Parallel Programming” 文章中。
直到 2005 年的5月,国际标准组织 Fortran 委员会决定将 CAF 包含在下一版本的 Fortran 语言规范草案中 (也就是今天我们常说的 Fortran 2008).
为什么今天要讨论和学习 CAF 呢?
因为它提供了一种非常简洁、直观的标注方法,来处理那些通常必须要通过消息传递(message-passing)模型来处理的数据分解问题;同时它本身使用的是自然的,类似于 Fortran 的语法。 这种语法独立于硬件架构,可以在分布式内存系统,共享内存系统和集群系统等中得到使用。
有了 CAF 扩展之后,传统的 Fortran 语言就变成了一个稳定、高效的并行编程语言。熟悉 Fortran 语言的编程者只需要学习和掌握几条新的语法规则,而这些新规则解决了任何并行编程模型中都必须克服的几个基本问题:任务分发,和数据分发。
先来看看任务分发。一个 CAF 的程序被复制为固定数量的若干份,每一个复制拥有自己独立的数据对象。程序的每一个内存复本被称作一个“镜像” (image). 每一个镜像都是独立地异步执行,因此各个镜像执行的路径也是不相同的。程序员可以通过若干种方法决定镜像的实际执行路径,比如说借助每一个镜像唯一的“索引值"(index), 使用通常的Fortran控制语句;或者通过显式的同步。 而在同步之间的代码段,编译器可以自由地使用各种常用的优化技巧,就好像只有一个镜像存在。
接下来,我们必须要考虑数据的分发过程。 Fortran语言的Co-array扩展,允许程序员可以使用一个非常类似于通常 Fortran 数组表示的语法,来表明不同内存镜像之间的关系,从而实现数据的分发。 在如下的例子中,一个新的对象,coarray ("集合数组“), 被添加到原有的语言规范中:
REAL, DIMENSION(N)[*] :: X,Y
X(:) = Y(:)[Q]
它声明了每个镜像都包含两个 real 类型的,大小为 N 的数组。 如果在每个镜像中的 Q 变量具有相同的值,那么赋值语句的效果就是,每个镜像都从镜像 Q 的数组 Y 复制数据,并且将它们复制到本地的数组 X 中。
在圆括号 "()" 中的数组下标,在一个内存镜像的范围内,继续遵循通常的 Fortran 规范。 而在方括号 "[]" 中的数组下标, 则提供了一种非常便捷的标注方法,来表明访问不同镜像的对象。 一个指向co-array的不包含方括号的引用,会被指向在当前本地内存中正在运行的镜像中的对象,因此 co-arrary仅当需要时才会使用,大部分代码和原先的没有什么区别。
如果和原有的 Fortran 90 数组的语法相结合,CAF 可以展现给我们一个非常强大的,却十分简洁的途径,来表达如何进行远程内存访问。下面是更多的一些例子:
A = B[PE] ! 从 B[PE] 中取值
B[PE] = A ! 复制给 B[PE]
B[:] = A ! 广播 A 的值
B[LIST] = A ! 广播 A 的值,只针对由LIST定义的所有镜像的一个子集
C(:) = B[:] ! 收集所有镜像的 B
S = MINVAL(Y[:]) ! 求所有镜像的变量 Y 的最小值 (reduce)
B(1:M)[1:N] = S ! 标量 S , 按照数组形状 (1:M,1:N) 的方式赋值
此外, CAF 还增加了若干 内部函数 (intrinsics), 比如:函数 NUM_IMAGES() 返回了当前程序镜像的总数量;而 函数 THIS_IMAGE() 返回了当前镜像的索引值( 显然应该在 1 和 NUM_IMAGES() 之间);而 子程序 SYNC_ALL() 则提供了一个全局的边界,要求在此之前的所有镜像中的所有操作必须全部结束后,任何镜像中后于此边界的语句才能继续执行。 当然通常情况下,只等待与之有关联的镜像会更加合适,执行速度也更快,SYNC_ALL(WAIT=LIST) 就提供了这种功能。 START_CRITICAL 和 END_CRITICAL 则提供了关键区的保护。
镜像和集合数组的同步,是 CAF 程序中最最重要的部分。例如,在下面这个例子中,我们需要实现一个固定顺序的累积求和:
REAL SUM[*]
CALL SYNC_ALL( WAIT=1 )
DO IMG= 2,NUM_IMAGES()
IF (IMG==THIS_IMAGE()) THEN
SUM = SUM + SUM[IMG-1]
ENDIF
CALL SYNC_ALL( WAIT=IMG )
ENDDO
虽然使用 SYNC_ALL 时仅仅等待某个正在运行中的镜像能够提高性能,但是我们仍然需要有 NUM_IMAGES() 次全局的同步。 可以考虑一个更好的方案,可以最小程度的减少同步
REAL SUM[*]
ME = THIS_IMAGE()
IF (ME.GT.1) THEN
CALL SYNC_TEAM( TEAM=(/ME-1,ME/) )
SUM = SUM + SUM[ME-1]
ENDIF
IF (ME.LT.NUM_IMAGES()) THEN
CALL SYNC_TEAM( TEAM=(/ME,ME+1/) )
ENDIF
现在每一个镜像最多只参与两次同步,并且仅仅发生在镜像顺序前后相邻的两个镜像之间。注意第一个 SYNC_TEAM 调用会和前一个镜像的第二个 SYNC_TEAM 调用匹配。这也充分展示了 CAF 同步机制的好处, 不仅可以提高数据并行算法的性能,还可以提供针对数据并行的隐式的程序流程控制。
现在你应该相信,Co-Array Fortran 确实不错吧。
后面我们会介绍如何结合 Intel Fortran Composer XE 2011 来学习和发挥 CAF 的巨大能量。
顺便说一句, 如果希望得到“最终”版本的 Fortran 2008 标准的电子版本,当然是英文的,请到英特尔软件网络(http://software.intel.com/en-us/forums/)相关的 Fortran 论坛中找吧。