Sun Studio 12:OpenMP API 用户指南

第 2 章 编译并运行 OpenMP 程序

本章介绍编译器和运行时选项,这些选项会影响使用 OpenMP API 的程序。

要在多线程环境下运行并行化的程序,必须在执行程序前设置 OMP_NUM_THREADS 环境变量。这会将程序可以创建的最大线程数通知给运行时系统。缺省值为 1。通常,为 OMP_NUM_THREADS 设置的值不大于目标平台上可用虚拟处理器的数量。将 OMP_DYNAMIC 设置为 FALSE,可使用由 OMP_NUM_THREADS 指定的线程数。

有关 Sun Studio 编译器和 OpenMP 的最新信息,请访问 Sun Developer Network 门户网站:http://developers.sun.com/sunstudio

2.1 要使用的编译器选项

要使用 OpenMP 指令实现显式并行化,请使用 ccCCf95 选项标志 -xopenmp 编译程序。此标志可带有可选关键字参数。(f95 编译器将 -xopenmp-openmp 作为同义词接受。)

-xopenmp 标志接受下列关键字子选项。


-xopenmp=parallel

启用 OpenMP pragma 的识别。-xopenmp=parallel 的最低优化级别是 -xO3。如有必要,编译器会将优化级别从较低级别更改为 -xO3,并发出警告。


-xopenmp=noopt

启用 OpenMP pragma 的识别。如果优化级别低于 -xO3,则编译器不会提升它。如果将优化级别显式设置为低于 -xO3 的级别,如 -xO2 -openmp=noopt,则编译器会报告错误。如果没有使用 -openmp=noopt 指定优化级别,则会识别 OpenMP pragma,并相应地并行化程序,但不执行优化。


-xopenmp=stubs

不再支持此选项。OpenMP 桩模块库是为方便用户而提供的。要编译一个调用 OpenMP 库例程但忽略 OpenMP pragmas 的 OpenMP 程序,请在编译该程序时不要使用 -xopenmp 选项,并且将目标文件与 libompstubs.a 库链接。例如,% cc omp_ignore.c -lompstubs

不支持同时与 libompstubs.a 和 OpenMP 运行时库 libmtsk.so 进行链接,因为这样可能会导致意外的行为。


-xopenmp=none

禁用对 OpenMP pragma 的识别,并且不更改优化级别。 

附加说明:

2.2 Fortran 95 OpenMP 验证

使用 f95 编译器的全局程序检查功能,可以对 Fortran 95 程序的 OpenMP 指令进行静态的过程间验证。使用 -XlistMP 标志进行编译可启用 OpenMP 检查。(来自 -XlistMP 的诊断消息会出现在另一种文件中,该文件的名称由源文件名和 .lst 扩展名构成)。编译器将诊断下列违规和并行化抑制因素:

例如,使用 -XlistMP 编译源文件 ord.f 会生成诊断文件 ord.lst


FILE  "ord.f"
     1  !$OMP PARALLEL
     2  !$OMP DO ORDERED
     3                  do i=1,100
     4                          call work(i)
     5                  end do
     6  !$OMP END DO
     7  !$OMP END PARALLEL
     8
     9  !$OMP PARALLEL
    10  !$OMP DO
    11                  do i=1,100
    12                          call work(i)
    13                  end do
    14  !$OMP END DO
    15  !$OMP END PARALLEL
    16                  end
    17                  subroutine work(k)
    18  !$OMP ORDERED
         ^
**** ERR-OMP:  It is illegal for an ORDERED directive to bind to a
directive (ord.f, line 10, column 2) that does not have the
ORDERED clause specified.
    19                  write(*,*) k
    20  !$OMP END ORDERED
    21                  return
    22                  end

本例中,WORK 子例程中的 ORDERED 指令收到有关第二个 DO 指令的诊断,因为该指令缺少 ORDERED 子句。

2.3 OpenMP 环境变量

OpenMP 规范定义了四个用于控制 OpenMP 程序执行的环境变量。下表对它们进行了概括。

表 2–1 OpenMP 环境变量

环境变量 

功能 

OMP_SCHEDULE

为指定了 RUNTIME 调度类型的 DOPARALLEL DOforparallel for 指令/pragma 设置调度类型。如果未定义,则使用缺省值 STATIC"type[,chunk]"

示例:setenv OMP_SCHEDULE 'GUIDED,4'

OMP_NUM_THREADS PARALLEL

设置在执行并行区域期间所要使用的线程数。使用 NUM_THREADS 子句或调用 OMP_SET_NUM_THREADS() 可以覆盖此值。如果未设置,则使用缺省值 1。value 是一个正整数。为与传统程序兼容,设置 PARALLEL 环境变量的效果与设置 OMP_NUM_THREADS 的效果相同。但如果将这两个环境变量都设置为不同的值,运行时库会发出一个错误消息。

示例:setenv OMP_NUM_THREADS 16

OMP_DYNAMIC

启用或禁用可用于执行并行区域的线程数的动态调整。如果未设置,则使用缺省值 TRUETRUEFALSE

示例:setenv OMP_DYNAMIC FALSE

OMP_NESTED

启用或禁用嵌套并行操作。

TRUEFALSE。缺省值为 FALSE

示例:setenv OMP_NESTED FALSE

其他多重处理环境变量也会影响 OpenMP 程序的执行,但它们不是 OpenMP 规范的一部分。下表对它们进行了概括。

表 2–2 多重处理环境变量

环境变量 

功能 

SUNW_MP_WARN

控制 OpenMP 运行时库发出的警告消息。如果设置为 TRUE,运行时库会给 stderr 发出警告消息;如果设置为 FALSE,则禁用警告消息。缺省值为 FALSE

OpenMP 运行时库能够检查很多常见的 OpenMP 违规行为,如错误的嵌套和死锁。运行时检查会增加程序执行的开销。请参见第 3 章,实现定义的行为

如果 SUNW_MP_WARN 设置为 TRUE,则运行时库会向 stderr 发出警告消息。如果程序注册一个回调函数以接受警告消息,则运行时库也将发出警告消息。程序可通过调用以下函数来注册用户回调函数:


   int sunw_mp_register_warn (void (*func)(void *));

回调函数的地址将作为参数传递给 sunw_mp_register_warn()。如果成功注册了回调函数,该函数将返回 0,如果注册失败则返回 1。

如果程序已注册了回调函数,libmtsk 将调用该注册的函数,将一个指针传递给包含错误消息的本地化字符串。从回调函数返回后,指向的内存将不再有效。

示例: 

setenv SUNW_MP_WARN TRUE

SUNW_MP_THR_IDLE

控制 OpenMP 程序中空闲线程的状态,这些空闲线程正在某个屏障处等待或者正在等待要处理的新并行区域。可以将该值设置为下列某个值:SPINSLEEPSLEEP( times)SLEEP(timems)SLEEP( timemc),其中 time 是一个整数,指定时间量,s、msmc 指定时间单位(分别为秒、毫秒和微秒)。

SPIN 指定空闲线程在屏障处等待时或等待要处理的新并行区域时应旋转。不带时间参数的 SLEEP 指定空闲线程应立即休眠。带时间参数的 SLEEP 指定线程进入休眠状态前应旋转等待的时间量。

缺省情况下,空闲线程经过一段时间的旋转等待后将进入休眠状态。SLEEP、SLEEP(0)、SLEEP(0s)、SLEEP(0ms)SLEEP(0mc) 都是等效的。

示例: 


setenv SUNW_MP_THR_IDLE SPIN 
setenv SUNW_MP_THR_IDLE SLEEP 
setenv SUNW_MP_THR_IDLE SLEEP(2s) 
setenv SUNW_MP_THR_IDLE SLEEP(20ms) 
setenv SUNW_MP_THR_IDLE SLEEP(150mc)

SUNW_MP_PROCBIND

该环境变量仅能在 Solaris 系统上工作。SUNW_MP_PROCBIND 环境变量可用于将 OpenMP 程序的线程绑定到正在运行的系统上的虚拟处理器。虽然可以通过处理器绑定来增强性能,但是如果将多个线程绑定到同一虚拟处理器,则会导致性能下降。有关详细信息,请参见2.4 Solaris 上的处理器绑定

SUNW_MP_MAX_POOL_THREADS

指定线程池的最大大小。线程池只包含 OpenMP 运行时库创建的非用户线程。它不包含主线程或由用户程序显式创建的任何线程。如果将此环境变量设置为零,则线程池将为空,并且所有并行区域都将由一个线程执行。如果未指定,则使用缺省值 1023。有关详细信息,请参见4.2 控制嵌套并行操作

SUNW_MP_MAX_NESTED_LEVELS

指定活动嵌套并行区域的最大深度。活动嵌套深度大于此环境变量值的任何并行区域将只由一个线程来执行。如果并行区域是 OpenMP 并行区域(其 IF 子句的值为 false),则不会将该区域视为活动区域。如果未指定,则使用缺省值 4。有关详细信息,请参见4.2 控制嵌套并行操作

STACKSIZE

设置每个线程的栈大小。值以千字节为单位。缺省线程栈大小在 32 位 SPARC V8 和 x86 平台上为 4 Mb,在 64 位 SPARC V9 和 x86 平台上为 8 Mb。

示例: 

setenv STACKSIZE 8192 将线程栈大小设置为 8 Mb

STACKSIZE 环境变量还接受后缀为 B(表示字节)、K(表示千字节)、M(表示兆字节)或 G(表示千兆字节)的数值。缺省单位为千字节。

SUNW_MP_GUIDED_WEIGHT

设置加权因子,该因子用于确定在使用 GUIDED 调度的循环中为线程分配的块的大小。该值应该是正浮点数,并且应用于程序中所有使用 GUIDED 调度的循环。如果未设置,则采用缺省值 2.0。

2.4 Solaris 上的处理器绑定

通过处理器绑定,程序员可指示 Solaris 操作系统在整个程序执行期间在同一处理器上运行该程序中的线程。(在 Linux 上不提供此功能。)

在将处理器绑定与静态调度一起使用时,将有益于展示某个数据重用模式的应用程序,在该模式中,由并行区域或工作共享区域中的线程访问的数据将位于上次调用的并行区域或工作共享区域的本地缓存中。

从硬件的角度看,计算机系统是由一个或多个物理处理器组成的。从操作系统的角度看,其中每个物理处理器都映射到可运行程序中的线程的一个或多个虚拟处理器。如果 n 个虚拟处理器可用,则可同时调度运行 n 个线程。根据系统的不同,虚拟处理器可能是处理器、内核,等等。例如,每个 UltraSPARC IV 物理处理器都具有两个内核;从 Solaris OS 的角度看,其中每个内核都是一个虚拟处理器,可以在其中安排要运行的线程。另一方面,UltraSPARC T1 物理处理器具有八个内核,每个内核可运行四个同时进行处理的线程;从 Solaris OS 的角度看,共有 32 个虚拟处理器,可以在其中安排要运行的线程。在 Solaris 操作系统上,可以使用 psrinfo(1M) 命令来确定虚拟处理器的数量。

当操作系统将线程绑定到处理器时,实际上是将线程绑定到特定的虚拟处理器而不是物理处理器。

当在 Solaris OS 下运行时,设置 SUNW_MP_PROCBIND 环境变量,以便将 OpenMP 程序中的线程绑定到特定的虚拟处理器。为 SUNW_MP_PROCBIND 指定的值可以是下列值之一:

请注意,上文提到的非负整数表示逻辑标识符 (ID)。逻辑 ID 可能不同于虚拟处理器 ID。下面将介绍二者之间的差异。

虚拟处理器 ID:

系统中的每个虚拟处理器都有唯一的处理器 ID。可以使用 Solaris 操作系统的 psrinfo(1M) 命令显示有关系统中处理器的信息,包括其处理器 ID。此外,还可以使用 prtdiag(1M) 命令显示系统配置和诊断信息。

可以使用 psrinfo -pv 列出系统中的所有物理处理器以及与每个物理处理器关联的虚拟处理器。

虚拟处理器 ID 可能是连续的,也可能是不连续的。例如,在具有 8 个 UltraSPARC IV 处理器(16 个内核)的 Sun Fire 4810 上,虚拟处理器 ID 可能是:0、1、2、3、8、9、10、11、512、513、514、515、520、521、522、523。

逻辑 ID:

如上所述,为 SUNW_MP_PROCBIND 指定的非负整数是逻辑 ID。逻辑 ID 是从 0 开始的连续整数。如果系统中可用的虚拟处理器数为 n,则其逻辑 ID 为 0、1、...、n-1(按 psrinfo(1M) 命令显示的顺序)。以下 Korn shell 脚本可用于显示从虚拟处理器 ID 到逻辑 ID 的映射。


#!/bin/ksh

NUMV= `psrinfo | fgrep "on-line" | wc -l `
set -A VID  `psrinfo | cut -f1 `

echo "Total number of on-line virtual processors = $NUMV"
echo

let "I=0"
let "J=0"
while [[ $I -lt $NUMV ]]
do
  echo "Virtual processor ID ${VID[I]} maps to logical ID ${J}"
  let "I=I+1"
  let "J=J+1"
done

在将单个物理处理器映射到多个虚拟处理器的系统上,了解哪些逻辑 ID 与属于同一物理处理器的虚拟处理器相对应会很有用。在以后的 Solaris 发行版中,可以使用以下 Korn shell 脚本来显示此信息。


#!/bin/ksh

NUMV= `psrinfo | grep "on-line" | wc -l `
set -A VLIST  `psrinfo | cut -f1 `
set -A CHECKLIST  `psrinfo | cut -f1 `

let "I=0"

while [ $I -lt $NUMV ]
do
  let "COUNT=0"
  SAMELIST="$I"

  let "J=I+1"

  while [ $J -lt $NUMV ]
  do
    if [ ${CHECKLIST[J]} -ne -1 ]
    then
      if [  `psrinfo -p ${VLIST[I]} ${VLIST[J]} ` = 1 ]
      then
    SAMELIST="$SAMELIST $J"
    let "CHECKLIST[J]=-1"
    let "COUNT=COUNT+1"
      fi
    fi
    let "J=J+1"
  done

  if [ $COUNT -gt 0 ]
  then
    echo "The following logical IDs belong to the same physical processor:"
    echo "$SAMELIST"
    echo " "
  fi

  let "I=I+1"
done

解释为 SUNW_MP_PROCBIND 指定的值:

如果为 SUNW_MP_PROCBIND 指定的值是非负整数,则该整数表示线程应绑定到的虚拟处理器的起始逻辑 ID。线程会从具有指定逻辑 ID 的处理器开始,以循环方式绑定到虚拟处理器,在绑定到逻辑 ID 为 n-1 的处理器后,返回到逻辑 ID 为 0 的处理器。如果为 SUNW_MP_PROCBIND 指定的值是包含两个或更多非负整数的列表,则线程将以循环方式绑定到具有指定逻辑 ID 的虚拟处理器。将不会使用其逻辑 ID 不是指定逻辑 ID 的处理器。

如果为 SUNW_MP_PROCBIND 指定的值是用减号 ("-") 分隔的两个非负整数,则线程将以循环方式绑定到如下范围的虚拟处理器:以第一个逻辑 ID 开头,并以第二个逻辑 ID 结尾。将不会使用其逻辑 ID 在此范围之外的处理器。

如果为 SUNW_MP_PROCBIND 指定的值不符合上述任何一种形式,或者给定的逻辑 ID 无效,则会发出一条错误消息,并终止程序的执行。

请注意,微任务化库 libmtsk 创建的线程数取决于环境变量、用户程序中的 API 调用和 num_threads 子句。SUNW_MP_PROCBIND 指定线程应绑定到的虚拟处理器的逻辑 ID。线程将以循环方式绑定到该组处理器。如果程序中使用的线程数少于 SUNW_MP_PROCBIND 指定的逻辑 ID 数,则程序将不使用某些虚拟处理器。如果线程数大于 SUNW_MP_PROCBIND 指定的逻辑 ID 数,则一些虚拟处理器将绑定多个线程。

2.5 栈和栈大小

正在执行的程序为执行该程序的初始线程维护主栈,并为每个从属线程维护不同的栈。栈是临时内存地址空间,用于保留子程序或函数引用调用期间的参数和自动变量。

通常,主栈的缺省大小为 8 兆字节。使用 f95 -stackvar 选项编译 Fortran 程序会强制在栈中分配局部变量和数组,就好像它们是自动变量。显式并行化的程序暗指对 OpenMP 程序使用 -stackvar,因为该选项可提高优化器将循环中的调用并行化的能力。(有关 -stackvar 标志的讨论,请参见《Fortran 用户指南》。)但是,如果为栈分配的内存不足,这会导致栈溢出。

使用 limit C-shell 命令或 ulimit ksh/sh 命令可显示或设置主栈的大小。

OpenMP 程序的每个从属线程均具有其自身的线程栈。此栈模拟初始(或主)线程栈,但对于线程是唯一的。线程的 PRIVATE 数组和变量(对于线程是局部的)在线程栈中分配。在 32 位 SPARC V8 和 x86 平台上,缺省大小为 4 兆字节;在 64 位 SPARC V9 和 x86 平台上,缺省大小为 8 兆字节。辅助线程栈的大小使用 STACKSIZE 环境变量来设置。


demo% setenv STACKSIZE 16384   <-将线程栈大小设置为 16 Mb (C shell)

demo$ STACKSIZE=16384          <-相同,使用 Bourne/Korn shell
demo$ export STACKSIZE

可能需要反复试验才能确定最佳栈大小。如果栈大小设置的太小,不足以满足线程的运行需要,可能会导致邻近线程中发生无提示数据损坏或段故障。如果无法确定是否发生栈溢出,请使用 -xcheck=stkovf 编译器选项编译 Fortran、C 或 C++ 程序,以强制在栈溢出时发生段故障。这样便可以在发生数据损坏前停止程序。(注:-xcheck=stkovf 编译器选项仅在 SPARC 系统上可用。)

2.6 使用线程分析器检查 OpenMP 程序

可以通过使用 Sun Studio 线程分析器工具检查 OpenMP 程序中是否存在数据争用和死锁。有关详细信息,请参阅线程分析器手册及 tha(1) 手册页。