为什么OMP_SET_DYNAMIC(1)切勿调整线程数(在Visual C 中)

Why does omp_set_dynamic(1) never adjust the number of threads (in Visual C++)?

本文关键字:线程 Visual 调整 SET OMP DYNAMIC 勿调整 为什么      更新时间:2023-10-16

如果我们查看omp_set_dynamic的Visual C 文档,则实际上是从OMP 2.0标准(第39页的3.1.7节(复制的:

>

如果[函数参数]评估为非零值,则可以自动调整用于执行即将到来的并行区域的线程数量,以自动调整运行时环境以最好地使用系统资源。结果,用户指定的线程数是最大线程计数。执行平行区域的团队中的线程数固定在该并行区域的持续时间内,并由omp_get_num_threads函数报告。

似乎很明显,omp_set_dynamic(1)允许实现使用少于并行区域的当前最大线程数(大概是为了防止在高负载下进行超级订阅(。对本段的任何合理阅读都会表明,应通过查询平行区域的omp_get_num_threads来观察所述减少。

(两个文档也将签名显示为void omp_set_dynamic(int dynamic_threads);。看来"用户指定的线程数"不是指dynamic_threads,而是指"使用其余的OpenMP接口指定的用户"(。

但是,无论我在omp_set_dynamic(1)下推动系统负载有多高,omp_get_num_threads的返回值(在平行区域内查询(都不会从测试程序中的最大值中变化。但是我仍然可以观察到omp_set_dynamic(1)omp_set_dynamic(0)之间的明显性能差异。

这是一个示例程序,用于复制该问题:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <cstdlib>
#include <cmath>
#include <omp.h>
#define UNDER_LOAD true
const int SET_DYNAMIC_TO = 1;
const int REPEATS = 3000;
const unsigned MAXCOUNT = 1000000;
std::size_t threadNumSum = 0;
std::size_t threadNumCount = 0;
void oneRegion(int i)
{
  // Pesudo-randomize the number of iterations.
  unsigned ui = static_cast<unsigned>(i);
  int count = static_cast<int>(((MAXCOUNT + 37) * (ui + 7) * ui) % MAXCOUNT);
#pragma omp parallel for schedule(guided, 512)
  for (int j = 0; j < count; ++j)
  {
    if (j == 0)
    {
      threadNumSum += omp_get_num_threads();
      threadNumCount++;
    }
    if ((j + i + count) % 16 != 0)
      continue;
    // Do some floating point math.
    double a = j + i;
    for (int k = 0; k < 10; ++k)
      a = std::sin(i * (std::cos(a) * j + std::log(std::abs(a + count) + 1)));
    volatile double out = a;
  }
}

int main()
{
  omp_set_dynamic(SET_DYNAMIC_TO);

#if UNDER_LOAD
  for (int i = 0; i < 10; ++i)
  {
    std::thread([]()
    {
      unsigned x = 0;
      float y = static_cast<float>(std::sqrt(2));
      while (true)
      {
//#pragma omp parallel for
        for (int i = 0; i < 100000; ++i)
        {
          x = x * 7 + 13;
          y = 4 * y * (1 - y);
        }
        volatile unsigned xx = x;
        volatile float yy = y;
      }
    }).detach();
  }
#endif

  std::chrono::high_resolution_clock clk;
  auto start = clk.now();
  for (int i = 0; i < REPEATS; ++i)
    oneRegion(i);
  std::cout << (clk.now() - start).count() / 1000ull / 1000ull << " ms for " << REPEATS << " iterations" << std::endl;
  double averageThreadNum = double(threadNumSum) / threadNumCount;
  std::cout << "Entered " << threadNumCount << " parallel regions with " << averageThreadNum << " threads each on average." << std::endl;
  std::getchar();
  return 0;
}

编译器版本:Microsoft(R(C/C 优化编译器版本19.16.27024.1 x64

例如GCC,该程序将对omp_set_dynamic(1)打印的averageThreadNum明显低于omp_set_dynamic(0)。但是在MSVC上,尽管性能差异30%(170s vs 230s(,在两种情况下都显示了相同的值。

如何解释?

在Visual C 中,执行循环的线程数在此示例中使用omp_set_dynamic(1)减少了,这解释了性能差异。

但是,与标准(和Visual C 文档(的任何善意解释相反, omp_get_num_threads不报告此减少

弄清楚多少个线程MSVC 实际使用多少个线程的方法是每个并行区域使用 em 循环迭代(或并行任务(的omp_get_thread_num。以下是在很少的环内性能开销中进行的一种方法:

// std::hardware_destructive_interference_size is not available in gcc or clang, also see comments by Peter Cordes:
// https://stackoverflow.com/questions/39680206/understanding-stdhardware-destructive-interference-size-and-stdhardware-cons
struct alignas(2 * std::hardware_destructive_interference_size) NoFalseSharing
{
    int flagValue = 0;
};
void foo()
{
  std::vector<NoFalseSharing> flags(omp_get_max_threads());
#pragma omp parallel for
  for (int j = 0; j < count; ++j)
  {
    flags[omp_get_thread_num()].flagValue = 1;
    // Your real loop body
  }
  int realOmpNumThreads = 0;
  for (auto flag : flags)
    realOmpNumThreads += flag.flagValue;
}

的确,您会发现realOmpNumThreads与在Visual C 上使用omp_set_dynamic(1)内的omp_get_num_threads()产生明显不同的值。


一个人可以说技术上

  • " 团队中的线程数执行并行区域"和
  • " 使用的线程数用于执行即将到来的并行区域"

实际上并不相同。

这是我认为对标准的毫无意义的解释,因为意图很明确,并且没有理由说" "团队中执行并行区域的线程数量停留固定在该并行区域的持续时间内,如果此数字与omp_set_dynamic的功能无关,则omp_get_num_threads函数 在本节中报告了。

但是,可能是MSVC决定将线程数保持在> Team 中,而不受影响,只有omp_set_dynamic(1)下,将执行的无循环迭代到其中的一个子集易于实施。

无论情况如何:在Visual C 中不信任omp_get_num_threads