C++ 为什么omp#u set#u dynamic(1)从不调整线程数(在Visual C+;+;)?

C++ 为什么omp#u set#u dynamic(1)从不调整线程数(在Visual C+;+;)?,c++,multithreading,visual-c++,openmp,C++,Multithreading,Visual C++,Openmp,如果我们查看omp_set_dynamic,它实际上是从粘贴的副本(第39页第3.1.7节): 如果[函数参数]的计算结果为非零值,则运行时环境可能会自动调整用于执行即将到来的并行区域的线程数,以最佳利用系统资源。因此,用户指定的线程数就是最大线程数。执行并行区域的团队中的线程数在该并行区域的持续时间内保持不变,并由omp\u get\u num\u threads函数报告 显然,omp\u set\u dynamic(1)允许实现为并行区域使用少于当前最大线程数的线程(可能是为了防止在高负载下

如果我们查看
omp_set_dynamic
,它实际上是从粘贴的副本(第39页第3.1.7节):

如果[函数参数]的计算结果为非零值,则运行时环境可能会自动调整用于执行即将到来的并行区域的线程数,以最佳利用系统资源。因此,用户指定的线程数就是最大线程数。执行并行区域的团队中的线程数在该并行区域的持续时间内保持不变,并由
omp\u get\u num\u threads
函数报告

显然,
omp\u set\u dynamic(1)
允许实现为并行区域使用少于当前最大线程数的线程(可能是为了防止在高负载下过度订阅)。对本段的任何合理解读都表明,通过查询平行区域内的
omp\u get\u num\u线程
,可以观察到上述减少

(这两份文档还将签名显示为
void omp\u set\u dynamic(int dynamic\u threads);
。似乎“用户指定的线程数”不是指
dynamic\u threads
,而是指“用户使用剩余OpenMP接口指定的任何内容”)

但是,无论我在
omp\u set\u dynamic(1)
下将系统负载推得多高,
omp\u get\u num\u threads
(在并行区域内查询)的返回值始终不会从测试程序中的最大值更改。但是我仍然可以观察到
omp\u set\u dynamic(1)
omp\u set\u 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;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#在加载为真的情况下定义
常量int设置为=1;
const int REPEATS=3000;
const unsigned MAXCOUNT=1000000;
std::size\u t threadNumSum=0;
std::size\u t threadNumCount=0;
无效区域(内部i)
{
//Pesudo将迭代次数随机化。
无符号ui=静态_转换(i);
int count=静态_cast(((最大计数+37)*(用户界面+7)*用户界面)%MAXCOUNT);
#计划的pragma omp并行(引导,512)
对于(int j=0;jSTD::VisualC++中的CUT< P>,执行循环<强>的线程的数目< < /St> >在代码中使用<代码> OppSeTySuxDype(1)< /代码>进行还原,这说明性能差异。

<>但是,与标准(和VisualC++ +文档)的任何诚实解释相反,<强> >代码> OMPGETGETNUMIX线程< /C> >不报告此减少< /强>

计算MSVC在每个并行区域实际使用的线程数的唯一方法是在每个循环迭代(或并行任务)上检查
omp\u get\u thread\u 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;
}
//std::硬件\u破坏性\u干扰\u大小在gcc或clang中不可用,另请参见Peter Cordes的评论:
// https://stackoverflow.com/questions/39680206/understanding-stdhardware-destructive-interference-size-and-stdhardware-cons
结构对齐(2*std::硬件\u破坏性\u干涉\u大小)无错误共享
{
int flagValue=0;
};
void foo()
{
向量标志(omp_get_max_threads());
#pragma-omp并行
对于(int j=0;j
< >实际上,您会发现 > OppGigGuthNothTythRead()/<代码>中的<代码> OMPPGETSySub动态(1)< /C> >在VisualC++中,将产生显著不同的值。
有人可能会说,从技术上讲

  • “执行并行区域的团队中的线程数”和
  • “用于执行即将到来的并行区域的线程数”
从字面上看是不一样的

在我看来,这是对标准的无意义解释,因为其意图非常明确,标准没有理由这样说“执行并行区域的团队中的线程数在该并行区域的持续时间内保持固定,并且由本节中的
omp\u get\u num\u threads
函数报告,如果该数目与
omp\u set\u dynamic
的功能无关

然而,可能是MSVC决定保持团队中的线程数量不受影响,只
omp\u set\u dynamic(1)
下为执行分配不循环迭代

不管是什么情况:<强>不信任代码> OppGigGuthNothOnthys<代码>,在VisualC++中。< /强> < /P>