乐趣区

关于并发:OpenMP-环境变量使用总结

OpenMP 环境变量应用总结

  • OMP_CANCELLATION,在 OpenMP 标准 4.5 当中规定了勾销机制,咱们能够应用这个环境变量去设置是否启动勾销机制,如果这个值等于 TRUE 那么就是开启线程勾销机制,如果这个值等于 FALSE 那么就是敞开勾销机制。
#include <stdio.h>
#include <omp.h>

int main()
{int s = omp_get_cancellation();
   printf("%d\n", s);
#pragma omp parallel num_threads(8) default(none)
   {if (omp_get_thread_num() == 2)
      {#pragma omp cancel parallel}

      printf("tid = %d\n", omp_get_thread_num());

   }
   return 0;
}

在下面的程序当中,如果咱们启动勾销机制,那么线程号等于 2 的线程就不会执行前面的 printf 语句。

➜  cmake-build-hun git:(master) ✗  export OMP_CANCELLATION=TRUE # 启动勾销机制
➜  cmake-build-hun git:(master) ✗ ./cancel 
1
tid = 0
tid = 4
tid = 1
tid = 3
tid = 5
tid = 6
tid = 7
  • OMP_DISPLAY_ENV,这个环境变量的作用就是程序在执行的时候首先会打印 OpenMP 相干的环境变量。如果这个环境变量值等于 TRUE 就会打印环境变量的值,如果是 FLASE 就不会打印。
➜  cmake-build-hun git:(master) ✗ export OMP_DISPLAY_ENV=TRUE   
➜  cmake-build-hun git:(master) ✗ ./critical 

OPENMP DISPLAY ENVIRONMENT BEGIN
  _OPENMP = '201511'
  OMP_DYNAMIC = 'FALSE'
  OMP_NESTED = 'FALSE'
  OMP_NUM_THREADS = '32'
  OMP_SCHEDULE = 'DYNAMIC'
  OMP_PROC_BIND = 'FALSE'
  OMP_PLACES = ''OMP_STACKSIZE ='0'OMP_WAIT_POLICY ='PASSIVE'OMP_THREAD_LIMIT ='4294967295'OMP_MAX_ACTIVE_LEVELS ='2147483647'OMP_CANCELLATION ='TRUE'OMP_DEFAULT_DEVICE ='0'OMP_MAX_TASK_PRIORITY ='0'OMP_DISPLAY_AFFINITY ='FALSE'OMP_AFFINITY_FORMAT ='level %L thread %i affinity %A'
OPENMP DISPLAY ENVIRONMENT END
data = 0
  • OMP_DYNAMIC,如果将这个环境变量设置为 true,OpenMP 实现能够调整用于执行并行区域的线程数,以优化系统资源的应用。与这个环境变量相干的一共有两个函数:
void omp_set_dynamic(int);
int omp_get_dynamic(void);

omp_set_dynamic 应用这个函数示意是否设置动静调整线程的个数,如果传入的参数不等于 0 示意开始,如果参数等于 0 就示意敞开动静调整。

咱们当初来谈一谈 dynamic 动静调整线程个数以优化系统资源的应用是什么意思,这个意思就是 OpenMP 创立的线程个数在同一个时刻不会超过你零碎的处理器的个数,因为 OpenMP 经常用在数据密集型工作当中,这类工作对 CPU 的需要大,因而为了充分利用资源,只会创立处理器个数的线程个数。

上面咱们应用一个例子来验证下面所谈到的内容。

#include <omp.h>
#include <stdio.h>

int main(int argc, char* argv[])
{//   omp_set_dynamic(1);

#pragma omp parallel num_threads(33) default(none)
   {printf("tid = %d\n", omp_get_thread_num());
   }

   return 0;
}

下面的代码如果咱们没有设置 OMP_DYNAMIC=TRUE 或者没有应用 omp_set_dynamic(1) 去启动静调整的话,那么下面的 printf 语句会被执行 33 次,然而如果你进行了设置,也就是启动了动静调整线程的个数的话,那么创立的线程个数就是 min(33, num_processors),后者是你的机器的处理器的个数,比方如果处理器的核的个数是 16 那么就只会有 16 个线程执行并行域当中的代码。

  • OMP_NESTED,这个示意是否开启并行域的嵌套模式,这个环境变量要么是 TRUE 或者 FALSE,如果这个环境变量的值为 TRUE 那么可能嵌套的最大的并行域的数量受到环境变量 OMP_MAX_ACTIVE_LEVELS 的限度,与这个环境变量相干的一个动静库函数为 void omp_set_nested(int nested);,示意是否开启嵌套的并行域。
  • OMP_NUM_THREADS,这个示意设置并行域的线程个数,与这个环境变量相干的有 num_threads 这个子句和动静库函数 void omp_set_num_threads(int num_threads); 也是相干的。他们的优先级为:num_threads > omp_set_num_threads > OMP_NUM_THREADS。这个环境变量的值必须是一个大于 0 的整数,对于他们的优先级你能够认为离并行域越远的就优先级越低,反之越高。
  • OMP_STACKSIZE,这个环境变量的次要作用就是设置一个线程的栈空间的大小。
  • OMP_WAIT_POLICY,这个参数的次要作用就是管制当线程没有拿到锁的时候是自旋获取锁还是进入内核被挂起。这个参数次要有两个值,active 或者 passive。

    • PASSIVE,期待的线程不耗费 CPU,而是进入内核挂起。
    • ACTIVE,期待的线程耗费 CPU,始终自旋获取锁。

咱们当初应用例子来验证下面的规定:

#include <stdio.h>
#include <omp.h>

int main()
{
   omp_lock_t lock;
   omp_init_lock(&lock);
#pragma omp parallel num_threads(16) default(none) shared(lock)
   {omp_set_lock(&lock);
      while (1);
      omp_unset_lock(&lock);
   }
   return 0;
}

在下面的代码当中有一个并行域,并行域中线程的个数是 16,咱们首先应用 ACTIVE 来看一下这个过程的负载,依据后面咱们的形容那么 16 个线程都会在自旋获取锁,这个过程将会始终应用 CPU,因而这个过程的负载 %CPU,应该是靠近 1600 %,每个线程都是 100% 加起来就是 1600 %。

➜  cmake-build-openmp export OMP_WAIT_POLICY=ACTIVE 
➜  cmake-build-openmp ./wait_policy                

咱们应用 top 命令查看一下这个过程的 CPU 使用率。

top - 17:27:14 up 263 days,  2:11,  2 users,  load average: 93.87, 87.59, 85.78
Tasks:  31 total,   2 running,  29 sleeping,   0 stopped,   0 zombie
%Cpu(s): 80.0 us,  0.7 sy,  0.0 ni, 19.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 13191648+total, 54673112 free, 15049648 used, 62193724 buff/cache
KiB Swap: 12499968+total, 11869649+free,  6303184 used. 11600438+avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
112290 root      20   0  133868   1576   1452 R  1600  0.0  11:52.84 wait_policy

依据下面的输入后果咱们能够看到咱们的预测是对的,所有的线程都沉闷的在应用 CPU。

当初咱们再来看一下如果咱们应用 PASSIVE 的状况会是怎么样的?依据后面的形容如果线程没有获取到锁那么就会被挂起,因为只可能有一个线程获取到锁,其余 15 个线程都将被挂起,因而 CPU 的使用率应该是 100 % 左右,这个线程就是那个获取到锁的线程。

➜  cmake-build-openmp export OMP_WAIT_POLICY=PASSIVE
➜  cmake-build-openmp ./wait_policy 

咱们再应用 top 命令查看一下对应的输入:

top - 17:27:53 up 263 days,  2:11,  2 users,  load average: 92.76, 88.10, 86.03
Tasks:  31 total,   2 running,  29 sleeping,   0 stopped,   0 zombie
%Cpu(s): 53.3 us,  0.8 sy,  0.0 ni, 45.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 13191648+total, 54675824 free, 15046932 used, 62193728 buff/cache
KiB Swap: 12499968+total, 11869649+free,  6303184 used. 11600710+avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
112317 root      20   0  133868   1624   1496 R  99.3  0.0   0:04.58 wait_policy

从下面的输入后果来看也是合乎咱们的预期,只有一个线程在一直的应用 CPU。

  • GOMP_SPINCOUNT,这个环境变量的次要作用就是当 OMP_WAIT_POLICY 是 active 的时候,最多忙期待自旋多少次,如果自旋的次数超过这个值的话,那么这个线程将会被挂起。

    当这个环境变量没有定义:

    • OMP_WAIT_POLICY=PASSIVE,那么自旋次数为 0。
    • 如果 OMP_WAIT_POLICY 也是未定义的话,那么这个自旋次数将会被设置成 300,000。
    • OMP_WAIT_POLICY=ACTIVE,那么自旋的次数是 300 亿次。

    另外如果 OpenMP 的线程的个数大于可用的 CPU 的外围的个数的时候,1000 和 100 次就是 GOMP_SPINCOUNT 的值,对应 OMP_WAIT_POLICY=ACTIVE 和 OMP_WAIT_POLICY 没有定义。

  • OMP_MAX_TASK_PRIORITY,这个是设置 OpenMP 工作的优先级的最大值,这个值应该是一个大于等于 0 的值,如果没有定义,默认优先级的值就是 0。
  • OMP_MAX_ACTIVE_LEVELS,这个参数的次要作用是设置最大的嵌套的并行域的个数。
  • GOMP_CPU_AFFINITY,这个环境变量的作用就是将线程绑定到特定的 CPU 外围上。该变量应蕴含以空格分隔或逗号分隔的 CPU 列表。此列表可能蕴含不同类型的条目:任意程序的单个 CPU 编号、CPU 范畴(M-N)或具备肯定步长的范畴(M-N:S)。CPU 编号从零开始。例如,GOMP_CPU_AFFINITY=“0 3 1-2 4-15:2”将别离将初始线程绑定到 CPU 0,第二个绑定到 CPU 3,第三个绑定到 CPU1,第四个绑定到 CPU 2,第五个绑定到 CPU 4,第六个到第十个绑定到 ccu 6、8、10、12 和 14,而后从列表的结尾开始重新分配。GOMP_CPU_AFFINITY= 0 将所有线程绑定到 CPU 0。

咱们当初来应用一个例子查看环境变量的应用。咱们的测试程序如下:

#include <stdio.h>
#include <omp.h>

int main()
{
   omp_lock_t lock;
   omp_init_lock(&lock);
#pragma omp parallel num_threads(4) default(none) shared(lock)
   {while (1);
   }
   return 0;
}

下面的程序就是开启四个线程而后进行死循环。在我的测试环境中一共有 4 个 CPU 计算外围。咱们当初执行下面的程序,对应的后果如下所示,上面的图是应用命令 htop 失去的后果:

➜  tmp ./a.out
────────────────────────────────────────────────────────────────────────────────

    0[||||||||||||||||||||||||100.0%]   Tasks: 118, 212 thr; 4 running
    1[||||||||||||||||||||||||100.0%]   Load average: 2.62 0.86 0.29
    2[||||||||||||||||||||||||100.0%]   Uptime: 04:21:10
    3[||||||||||||||||||||||||100.0%]
  Mem[||||||||||||||||||||575M/3.82G]
  Swp[0K/3.82G]

    PID USER      PRI  NI  VIRT   RES   SHR S CPU%▽MEM%   TIME+  Command
  10750 lehung     20   0 27304   852   756 R 400.  0.0  2:30.53 ./a.out

从下面 htop 命令的输入后果能够看到 0 – 3 四个外围都跑满了,咱们当初来看一下如果咱们应用 GOMP_CPU_AFFINITY 环境变量应用线程绑定的形式 CPU 的负载将会是什么样!上面咱们将所有的线程绑定到 0 1 两个外围,那么依据咱们之前的剖析 0 号外围上将会有第一个和第三个线程,1 号外围将会有第二个和第四个线程在下面运行。

➜  tmp export GOMP_CPU_AFFINITY="0 1"
➜  tmp ./a.out
────────────────────────────────────────────────────────────────────────────────

    0[||||||||||||||||||||||||100.0%]   Tasks: 118, 213 thr; 4 running
    1[||||||||||||||||||||||||100.0%]   Load average: 2.29 1.10 0.41
    2[|                         1.3%]   Uptime: 04:22:03
    3[|                         0.7%]
  Mem[||||||||||||||||||||576M/3.82G]
  Swp[0K/3.82G]

    PID USER      PRI  NI  VIRT   RES   SHR S CPU%▽MEM%   TIME+  Command
  10772 lehung     20   0 27304   840   744 R 200.  0.0  0:10.42 ./a.out

其实与下面的过程相干的两个次要的零碎调用就是:

int sched_setaffinity(pid_t pid, size_t cpusetsize,
                             const cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize,
                             cpu_set_t *mask);

感兴趣的同学可能查看一下下面的两个函数的手册。

  • OMP_SCHEDULE,这个环境变量次要是用在 OpenMP 对于 for 循环的调度上的,他的规定为 OMP_SCHEDULE=type[,chunk],其中 type 的取值能够为 static, dynamic, guided, auto。并且 chunk size 是可选的,而且他的值是一个正整数。如果这个环境变量没有定义,默认的调度形式是 dynamic 并且 chunk size = 1。

总结

在本篇文章当中次要给大家介绍了一些常常应用的 OpenMP 零碎环境变量,设置环境变量有时候可能更加不便的设置程序,同时有些环境变量对应一些 OpenMP 的动静库函数。以上就是本篇文章的所有内容心愿大家有所播种!


更多精彩内容合集可拜访我的项目:https://github.com/Chang-LeHu…

关注公众号:一无是处的钻研僧,理解更多计算机(Java、Python、计算机系统根底、算法与数据结构)常识。

退出移动版