Openmp Runtime 庫函式彙總(上)

一無是處的研究僧發表於2023-01-15

Openmp Runtime 庫函式彙總(上)

  • omp_in_parallel,如果當前執行緒正在並行域內部,則此函式返回true,否則返回false。
#include <stdio.h>
#include <omp.h>

int main()
{
   printf("0> Not In parallel region value = %d\n",
          omp_in_parallel());

   // 在這裡函式返回的 false 在 C 語言當中返回值等於 int 型別的 0
   if (omp_in_parallel())
   {
      printf("1> In parallel region value = %d\n",
             omp_in_parallel());
   }
   #pragma omp parallel num_threads(2) default(none)
   {
      // 這裡函式的返回值是 1 在 C 語言當中對應 int 型別的 1
      if (omp_in_parallel())
      {
         printf("2> In parallel region value = %d\n",
                omp_in_parallel());
      }
   }
   return 0;
}

上面的程式的輸出結果如下所示:

0> Not In parallel region value = 0
2> In parallel region value = 1
2> In parallel region value = 1

需要注意的是在上面的函式 omp_in_parallel 使用時需要注意,如果你的並行域只有一個執行緒的時候 omp_in_parallel 返回的是 false。比如下面的例子:

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

int main()
{
   printf("0> Not In parallel region value = %d\n",
          omp_in_parallel());

   // 在這裡函式返回的 false 在 C 語言當中返回值等於 int 型別的 0
   if (omp_in_parallel())
   {
      printf("1> In parallel region value = %d\n",
             omp_in_parallel());
   }
  // 只有一個執行緒,因此並不會啟用並行域
   #pragma omp parallel num_threads(1) default(none)
   {
      // 這裡函式的返回值是 1 在 C 語言當中對應 int 型別的 1
      if (omp_in_parallel())
      {
         printf("2> In parallel region value = %d\n",
                omp_in_parallel());
      }
   }
   return 0;
}

在上面的程式當中,因為並行域當中只有一個執行緒,因此不會啟用,所以即使在 parallel 程式碼塊內不,這個 omp_in_parallel 也不會被觸發,上面的程式只會輸出第一個 printf 的內容:

0> Not In parallel region value = 0
  • omp_get_thread_num,這個函式的主要作用是用於返回當前並行域的執行緒組當中的唯一 ID,在一個序列程式碼當中,這個函式的返回結果之中等於 0 ,在並行域當中這個函式的返回值的區間等於 [0, num_threads - 1],如果是一個執行緒組的 master 執行緒呼叫這個函式,這個函式的返回值始終是 0,對應的測試程式碼如下所示:
#include <stdio.h>
#include <omp.h>

int main()
{
   printf("Non parallel region: omp_get_thread_num() = %d\n", omp_get_thread_num());

#pragma omp parallel num_threads(5) default(none)
   {
      printf("In parallel region: omp_get_thread_num() = %d\n", omp_get_thread_num());
#pragma omp master
      {
         printf("In parallel region master: omp_get_thread_num() = %d\n", omp_get_thread_num());
      }
   }

   return 0;
}

上面的程式的輸出結果如下所示:

Non parallel region: omp_get_thread_num() = 0
In parallel region: omp_get_thread_num() = 0
In parallel region master: omp_get_thread_num() = 0
In parallel region: omp_get_thread_num() = 4
In parallel region: omp_get_thread_num() = 1
In parallel region: omp_get_thread_num() = 2
In parallel region: omp_get_thread_num() = 3

在上面的程式碼當中我們可以看到,在非並行域當中(第一個 printf) 函式的輸出結果是 0,在並行域當中程式的輸出結果範圍是 [0. num_threads],如果一個執行緒是 master 執行緒的話,那麼它對應的執行緒組當中的返回值就是0。

  • omp_get_team_size,這個函式的主要作用就是返回一個執行緒組當中的執行緒個數。這個函式接受一個引數,他的函式原型為 int omp_get_team_size(int level);,這個引數的 level 的含義就是並行域的巢狀層級,這個引數的範圍是 [0, omp_get_level],如果傳入的引數不在這個範圍,那麼這個函式的返回值為 -1,如果你在一個並行域傳入的 level 的引數是 omp_get_level ,那麼這個函式的返回值和 omp_get_num_threads 的返回值相等。
#include <stdio.h>
#include <omp.h>

int main()
{
   omp_set_nested(1);
   int level = omp_get_level();
   int size = omp_get_team_size(level);
   printf("Non parallel region : level = %d size = %d\n", level, size);

#pragma omp parallel num_threads(5) default(none)
   {
      int level = omp_get_level();
      int size = omp_get_team_size(level);
      printf("level = %d size = %d\n", level, size);

#pragma omp parallel num_threads(2) default(none)
      {
         int level = omp_get_level();
         int size = omp_get_team_size(level);
         printf("level = %d size = %d\n", level, size);

         printf("Up one level team size = %d\n", omp_get_team_size(level - 1));
      }
   }
   return 0;
}

上面的程式的輸出結果如下所示:

Non parallel region : level = 0 size = 1
level = 1 size = 5
level = 1 size = 5
level = 1 size = 5
level = 1 size = 5
level = 1 size = 5
level = 2 size = 2
Up one level team size = 5
level = 2 size = 2
Up one level team size = 5
level = 2 size = 2
Up one level team size = 5
level = 2 size = 2
level = 2 size = 2
Up one level team size = 5
level = 2 size = 2
Up one level team size = 5
level = 2 size = 2
Up one level team size = 5
Up one level team size = 5
level = 2 size = 2
Up one level team size = 5
level = 2 size = 2
Up one level team size = 5
level = 2 size = 2
Up one level team size = 5

在上面的程式碼當中在非並行域的程式碼當中,程式的輸出結果和我們上面談到的是相同的,level 等於 0,team size 等於 1,在並行域當中輸出的結果也是符合預期的,我們來看一下最後兩個輸出,倒數第一個輸出是檢視上一個 level 的輸出結果,可以看到他的輸出結果等於 5,這和我們設定的 5 個執行緒是相符的。

  • omp_get_num_procs,這個函式相對比較簡單,主要是返回你的系統當中處理器的數量。
#include <stdio.h>
#include <omp.h>

int main()
{
   int nprocs = omp_get_num_procs();
   printf("Number of processors = %d\n", nprocs);
   return 0;
}
  • omp_in_final,這個函式主要是檢視當前執行緒是否在最終任務或者包含任務當中,比如在下面的例子當中,我們就可以測試這個函式:
#include <stdio.h>
#include <omp.h>

int echo(int n)
{
   int ret;
#pragma omp task shared(ret, n) final(n <= 10)
   {
      if(omp_in_final())
      {
         printf("In final n = %d\n", n);
         ret = n;
      }
      else
      {
         ret = 1 + echo(n - 1);
      }
   }
   return ret;
}

int main()
{
   printf("echo(100) = %d\n", echo(100));
   return 0;
}

上面的程式的輸出結果如下所示:

In final n = 10
echo(100) = 100

在上面的程式我們在函式 echo 當中定義了一個任務,當引數 n <= 10 的時候,這個任務會變成一個 final 任務,因此在 n == 10 的時候,這個函式的 omp_in_final 會返回 true,因此會直接將 10 賦值給 ret ,不會進行遞迴呼叫,因此我們得到了上面的輸出結果。

  • omp_get_nested ,這個函式是用於判斷是否開啟並行區域的巢狀,如果開啟了並行區域的巢狀,那麼這個函式就返回 true ,否則就返回 false ,對應 C 語言的值分別為 1 和 0。在 OpenMP 當中預設是關閉的,你可以使用函式 omp_set_nested(1) 進行開啟,或者使用 omp_set_nested(0) 關閉並行區域的巢狀。你可以對並行區域的巢狀有所疑惑,我們來看下面兩個例子,你就豁然開朗了。
#include <stdio.h>
#include <omp.h>


int main()
{
//   omp_set_nested(1);
   #pragma omp parallel num_threads(2) default(none)
   {
      printf("Outer tid = %d level = %d num_threads = %d\n",
             omp_get_thread_num(), omp_get_active_level(),
             omp_get_num_threads());
      #pragma omp parallel num_threads(2) default(none)
      {
         printf("Inner tid = %d level = %d num_threads = %d\n",
                omp_get_thread_num(), omp_get_active_level(),
                omp_get_num_threads());
      }
   }
   return 0;
}

上面的程式的輸出結果如下所示:

Outer tid = 0 level = 1 num_threads = 2
Inner tid = 0 level = 1 num_threads = 1
Outer tid = 1 level = 1 num_threads = 2
Inner tid = 0 level = 1 num_threads = 1

我們再來看一下如果我們開啟了並行區域的巢狀之後程式的輸出結果:

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


int main()
{
   omp_set_nested(1);
   #pragma omp parallel num_threads(2) default(none)
   {
      printf("Outer tid = %d level = %d num_threads = %d\n",
             omp_get_thread_num(), omp_get_active_level(),
             omp_get_num_threads());
      #pragma omp parallel num_threads(2) default(none)
      {
         printf("Inner tid = %d level = %d num_threads = %d\n",
                omp_get_thread_num(), omp_get_active_level(),
                omp_get_num_threads());
      }
   }
   return 0;
}

上面的程式的輸出結果如下所示:

Outer tid = 0 level = 1 num_threads = 2
Outer tid = 1 level = 1 num_threads = 2
Inner tid = 0 level = 2 num_threads = 2
Inner tid = 1 level = 2 num_threads = 2
Inner tid = 0 level = 2 num_threads = 2
Inner tid = 1 level = 2 num_threads = 2

我們可以對比一下兩個程式的輸出結果的差異,我們可以看到當我們允許並行巢狀之後,程式的輸出結果會更多一點,這是因為如果我們沒有開啟的話,子並行域每個執行緒只會啟動一個執行緒,如果我們開啟的話,那麼每個執行緒就會重新啟動 num_threads 個執行緒,比如在上面兩個程式碼當中,對於第一份程式碼外部並行塊會啟動兩個執行緒,然後在每個執行緒的內部有一個並行塊,但是因為沒有啟動,因此不管你的 num_threads 設定成多少,每個執行緒只會啟動一個執行緒,因此最終會有 4 個 printf 語句輸出。而對於第二份程式碼,是啟動了巢狀程式碼的,外部並行塊有兩個執行緒,在內部因為是開啟了,這兩個執行緒會分別建立 num_threads 個內部執行緒去執行內部程式碼塊,因此會有 2 個外部執行緒 4 (2x2)個內部執行緒,一共會有 6 個輸出,因此就符合上面的結果了。其實不僅可以巢狀兩層,還可以巢狀更多層,原理也是一樣的分析方法。

  • omp_get_active_level,這個函式主要是返回當前執行緒所在的並行域的巢狀的並行塊的級別,從 1 開始,從外往內沒加一層這個值就加一,如果沒有啟動並行巢狀,那麼這個函式的返回值就是 1。

  • omp_set_max_active_levels,我們在上面提到了,我們可以不斷的進行並行域的巢狀,我們可以使用這個函式進行設定最大的巢狀的層數,如果超過這個層數那麼就與不啟動巢狀的效果一樣了,也就是說 num_threads 不會發生效果了。

我們現在設定允許設定的最大的並行塊的巢狀層數等於 2,但是我們一共有三個巢狀塊,我們可以看一下對第三層巢狀的程式碼的影響。

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

int main()
{
   omp_set_nested(1);
   omp_set_max_active_levels(2);

#pragma omp parallel num_threads(2) default(none)
   {
      printf("1> omp_get_level = %d omp_get_active_level = %d\n", omp_get_level(), omp_get_active_level());
#pragma omp parallel num_threads(2) default(none)
      {
         printf("2> omp_get_level = %d omp_get_active_level = %d\n", omp_get_level(), omp_get_active_level());
#pragma omp parallel num_threads(2) default(none)
         {
            printf("3> omp_get_level = %d omp_get_active_level = %d\n", omp_get_level(), omp_get_active_level());

         }
      }
   }
   return 0;
}

上面的程式的輸出結果如下所示(3> 一共輸出了 4 次,因為它的外部執行緒的個數是 4 (2x2)):

1> omp_get_level = 1 omp_get_active_level = 1
1> omp_get_level = 1 omp_get_active_level = 1
2> omp_get_level = 2 omp_get_active_level = 2
3> omp_get_level = 3 omp_get_active_level = 2
2> omp_get_level = 2 omp_get_active_level = 2
2> omp_get_level = 2 omp_get_active_level = 2
3> omp_get_level = 3 omp_get_active_level = 2
2> omp_get_level = 2 omp_get_active_level = 2
3> omp_get_level = 3 omp_get_active_level = 2
3> omp_get_level = 3 omp_get_active_level = 2

我們現在將函式 omp_set_max_active_levels 的引數設定成 3,也就是說允許的最大的並行塊的巢狀層數等於3,重新看一下程式的輸出結果是什麼(將 omp_set_max_active_levels(2) 改成 omp_set_max_active_levels(3))之後,程式的輸出結果如下所示:

1> omp_get_level = 1 omp_get_active_level = 1
1> omp_get_level = 1 omp_get_active_level = 1
2> omp_get_level = 2 omp_get_active_level = 2
2> omp_get_level = 2 omp_get_active_level = 2
2> omp_get_level = 2 omp_get_active_level = 2
2> omp_get_level = 2 omp_get_active_level = 2
3> omp_get_level = 3 omp_get_active_level = 3
3> omp_get_level = 3 omp_get_active_level = 3
3> omp_get_level = 3 omp_get_active_level = 3
3> omp_get_level = 3 omp_get_active_level = 3
3> omp_get_level = 3 omp_get_active_level = 3
3> omp_get_level = 3 omp_get_active_level = 3
3> omp_get_level = 3 omp_get_active_level = 3
3> omp_get_level = 3 omp_get_active_level = 3

可以看到 3> 的輸出次數等於8,這個次數就表明外部 4 個執行緒都在第三個並行塊產生了兩個執行緒,這就是啟動巢狀並行塊的效果,這就是設定函式 omp_set_max_active_levels 的效果。

  • omp_get_nested,這個函式的作用就是返回是否啟動了並行巢狀,在 C 語言當中如果啟動了那麼就是返回 1,否則就是返回 0。
#include <stdio.h>
#include <omp.h>

int main()
{
   printf("omp_get_nested() = %d\n", omp_get_nested());
   omp_set_nested(1);
   printf("omp_get_nested() = %d\n", omp_get_nested());
   return 0;
}

上面的程式的輸出結果如下所示:

omp_get_nested() = 0
omp_get_nested() = 1

總結

在本篇文章當中主要給大家介紹了一些在 OpenMP 當中常用的動態庫函式,這篇文章的動態庫函式主要是關於並行域和執行緒狀態的函式,在下篇文章當中我們主要是分析一些 OpenMP 當中的鎖相關函式。希望大家有所收穫!


更多精彩內容合集可訪問專案:https://github.com/Chang-LeHung/CSCore

關注公眾號:一無是處的研究僧,瞭解更多計算機(Java、Python、計算機系統基礎、演算法與資料結構)知識。

相關文章