Oracle Solaris Studio 12.2:OpenMP API 用户指南

第 2 章 编译并运行 OpenMP 程序

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


注 –

要在多线程环境下运行并行化程序,必须将程序中的线程数设置为大于一。设置方法:在运行程序之前将 OMP_NUM_THREADS 环境变量设置为大于一的值,或从正在运行的程序中调用 omp_set_num_threads() 函数,或在 PARALLEL 指令中使用 num_threads 子句。


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 程序但忽略其 OpenMP pragma,请在编译该程序时不要使用 -xopenmp 选项,并且将目标文件与 libompstubs.a 库链接。

例如,% cc omp_ignore.c -lompstubs

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


-xopenmp=none

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

附加说明:

2.2 OpenMP 环境变量

OpenMP 规范定义了若干用于控制 OpenMP 程序执行的环境变量。在2.2.1 常见的 OpenMP 环境变量中对这些变量进行了总结。有关详细信息,请参阅 openmp.org 中的 OpenMP API 版本 3.0 规范。此版本的 Solaris Studio 编译器定义了未包括在 OpenMP 规范中的其他环境变量,在2.2.2 Solaris Studio 特定的环境变量中对这些变量进行了总结。

2.2.1 常见的 OpenMP 环境变量

OMP_SCHEDULE

为指定了 RUNTIME 调度类型的 DOPARALLEL DOforparallel for 指令/pragma 设置调度类型。

如果未定义,则使用缺省值 STATICvalue"type[,chunk]"

示例:setenv OMP_SCHEDULE 'GUIDED,4'

OMP_NUM_THREADS

设置要在执行并行区域期间使用的线程数。

可以使用 num_threads 子句或通过调用 omp_set_num_threads() 来覆盖此值。

如果未设置,则使用缺省值 1。value 是一个正整数。

示例:setenv OMP_NUM_THREADS 16

OMP_DYNAMIC

启用或禁用可用于执行并行区域的线程数的动态调整。

如果未设置,则使用缺省值 TRUETRUEFALSE

示例:setenv OMP_DYNAMIC FALSE

OMP_NESTED

启用或禁用嵌套的并行性。

TRUEFALSE

缺省值为 FALSE

示例:setenv OMP_NESTED FALSE

OMP_STACKSIZE

为 OpenMP 创建的线程设置栈大小。

可以将大小指定为以千字节为单位的正整数,或者带有后缀 B、K、MG,分别表示字节、千字节、兆字节或千兆字节。

示例:setenv OMP_STACKSIZE 10M

OMP_WAIT_POLICY

设置正在等待的线程的所需策略:ACTIVEPASSIVE

ACTIVE 线程在等待时会占用处理器时间。PASSIVE 线程不会占用处理器时间,并且可能会放弃处理器或进入休眠状态。

OMP_MAX_ACTIVE_LEVELS

将嵌套活动并行区域的最大级别数设置为非负整数值。

OMP_THREAD_LIMIT

将要在整个 OpenMP 程序中使用的线程数设置为正整数。

2.2.2 Solaris Studio 特定的环境变量

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

PARALLEL

为与传统程序兼容,设置 PARALLEL 环境变量的效果与设置 OMP_NUM_THREADS 的效果相同。然而,如果同时设置 PARALLELOMP_NUM_THREADS,则必须将它们设置为相同的值。

SUNW_MP_WARN

控制 OpenMP 运行时库发出的警告消息。如果将 SUNW_MP_WARN 设置为 TRUE,运行时库会向 stderr 发送警告消息。此外,运行时库还会输出所有环境变量的设置,以供参考。如果将该环境变量设置为 FALSE,运行时库将不发送任何警告消息或输出任何设置。缺省值为 FALSE

OpenMP 运行时库能够检查很多常见的 OpenMP 违规行为,如错误的嵌套和死锁。运行时检查会增加程序执行的开销。请参见第 3 章。如果 SUNW_MP_WARN 设置为 TRUE,则运行时库会向 stderr 发出警告消息。

示例:

setenv SUNW_MP_WARN TRUE

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


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

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

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


注 –

测试或调试程序时,请将 SUNW_MP_WARN 设置为 TRUE。这样您便可以查看来自 OpenMP 运行时库的警告消息。


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

SUNW_MP_MAX_POOL_THREADS

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

请注意,SUNW_MP_MAX_POOL_THREADS 指定用于整个程序的非用户 OpenMP 线程的最大数量,而 OMP_THREAD_LIMIT 指定用于整个程序的用户和非用户 OpenMP 线程的最大数量。如果同时设置 SUNW_MP_MAX_POOL_THREADSOMP_THREAD_LIMIT,则它们的值必须一致,以便将 OMP_THREAD_LIMIT 设置为比 SUNW_MP_MAX_POOL_THREADS 的值大一。

SUNW_MP_MAX_NESTED_LEVELS

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

请注意,如果同时设置 SUNW_MP_MAX_NESTED_LEVELSOMP_MAX_ACTIVE_LEVELS,则必须将它们设置为相同的值。

STACKSIZE

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

示例:

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

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

请注意,如果同时设置 STACKSIZEOMP_STACKSIZE,则必须将它们设置为相同的值。

SUNW_MP_GUIDED_WEIGHT

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

SUNW_MP_WAIT_POLICY

控制程序中等待工作(空闲)、在屏障处等待或等待任务的线程的行为。上述各种等待类型的行为有三种可能:旋转片刻、让出 CPU 片刻、休眠直至被唤醒。

语法为(使用 csh 显示):

setenv SUNW_MP_WAIT_POLICY IDLE=val :BARRIER=val:TASKWAIT= val

IDLE=valBARRIER= valTASKWAIT= val 是可选关键字,用于指定所控制的等待类型。

对于上述每个关键字,都有一个 val 设置来描述等待行为:SPINYIELDSLEEP

SPIN(time) 指定线程在让出 CPU 之前应旋转多长时间。time 可以是秒、毫秒或微秒(分别用 smsmc 表示)。如果不指定时间单位,则使用秒。如果 SPIN 不带时间参数,表示线程在等待时应持续旋转。

YIELD(number) 指定线程在休眠之前应让出 CPU 的次数。每次让出 CPU 后,线程会在操作系统将其调度为运行时再次运行。如果 YIELD 不带 number 参数,表示线程在等待时应持续让出。

SLEEP 指定线程应立即转入休眠。

请注意,可以按任意顺序指定特定等待类型的 SPINSLEEPYIELD 设置。这些设置之间用逗号分隔。"SPIN(0),YIELD(0)"SLEEP(立即休眠)相同。在处理 IDLEBARRIERTASKWAIT 的设置时,采用左侧优先规则。

示例:

% setenv SUNW_MP_WAIT_POLICY “BARRIER=SPIN”

在屏障等待的线程将一直旋转,直到组中的所有线程都到达该屏障。

% setenv SUNW_MP_WAIT_POLICY “IDLE=SPIN(10ms),YIELD(5)”

等待工作(空闲)的线程旋转 10 毫秒,然后让出 CPU 5 次,再转入休眠。

% setenv SUNW_MP_WAIT_POLICY “IDLE=SPIN(10ms),YIELD(2):BARRIER=SLEEP:TASKWAIT=YIELD(10)”

等待工作(空闲)的线程旋转 10 毫秒,然后让出 CPU 2 次,再转入休眠;在屏障处等待的线程让出 CPU 10 次,再转入休眠。

2.3 处理器绑定

通过处理器绑定,程序员可指示操作系统在整个程序执行期间在同一处理器上运行该程序中的线程。

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

从硬件的角度看,计算机系统是由一个或多个物理处理器组成的。从操作系统的角度看,其中每个物理处理器都映射到可运行程序中的线程的一个或多个虚拟处理器。如果 n 个虚拟处理器可用,则可同时调度运行 n 个线程。根据系统的不同,虚拟处理器可能是处理器、内核,等等。

例如,UltraSPARC T2 物理处理器具有八个内核,每个内核可运行八个同时进行处理的线程;从 Solaris OS 的角度看,共有 64 个虚拟处理器,可以在其中调度要运行的线程。在 Solaris 平台上,可以使用 psrinfo(1M) 命令来确定虚拟处理器的数量。在 Linux 系统上,文件 /proc/cpuinfo 提供有关可用处理器的信息。

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

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

2.3.3 解释为 SUNW_MP_PROCBIND 指定的值中显示了对 SUNW_MP_PROCBIND 所接受的值的解释。

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

2.3.1 虚拟处理器 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。

2.3.2 逻辑 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

2.3.3 解释为 SUNW_MP_PROCBIND 指定的值

如果为 SUNW_MP_PROCBIND 指定的值为 TRUE,线程将以循环(共享)方式绑定到虚拟处理器。绑定的起始处理器由运行时库以获得最佳性能为目标进行确定。

如果为 SUNW_MP_PROCBIND 指定的目标为 FALSE,线程将不绑定到任何处理器。这是缺省设置。

如果为 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 无效,则会发出一条错误消息,并终止程序的执行。

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

2.3.4 与 OS 处理器集进行交互

在 Solaris 平台上使用 psrset 实用程序,或者在 Linux 平台上使用 taskset 命令,可以指定处理器集。SUNW_MP_PROCBIND 没有将处理器集考虑在内。如果程序员使用处理器集,则他们应负责确保 SUNW_MP_PROCBIND 的设置与所用的处理器集一致。否则,在 Linux 系统上,SUNW_MP_PROCBIND 的设置将覆盖处理器集设置,而在 Solaris 系统上,将会发出错误消息。

2.4 栈和栈大小

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

通常,主栈的缺省大小为 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 兆字节。从属线程栈的大小通过 OMP_STACKSIZE 环境变量来设置。


demo% setenv OMP_STACKSIZE 16384   <-Set thread stack size to 16 Mb (C shell)

demo$ OMP_STACKSIZE=16384          <-Same, using Bourne/Korn shell
demo$ export OMP_STACKSIZE

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

2.5 检查和分析 OpenMP 程序

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

可以使用 Solaris Studio 性能分析器来分析 OpenMP 程序的性能。有关详细信息,请参考性能分析器手册或 collect(1) 和 analyzer(1) 手册页。