【问题标题】:OpenMP threads appear to execute seriallyOpenMP 线程似乎是串行执行的
【发布时间】:2011-04-03 10:23:28
【问题描述】:

我有一个应用程序,它基本上应该并行计算数学表达式的反向波兰符号。我的问题是我在使用 OpenMP 时没有看到任何性能提升。 (我用的是VS2008,设置了/openmp编译选项。)

我的主循环如下所示:

int nMaxThreads = std::min(omp_get_max_threads(), s_MaxNumOpenMPThreads);
int nThreadID;
omp_set_num_threads(nMaxThreads);

#pragma omp parallel for schedule(static) private(nThreadID)
for (i=0; i<nBulkSize; ++i)
{
  nThreadID = omp_get_thread_num();
  printf("Thread %d Idx %d start",nThreadID, i);
  results[i] = EvalRPNInParallel(i, nThreadID);
  printf(" -- %d Idx %d end\n",nThreadID, i);
}

printfs 仅用于调试目的,以查看是否正在发生任何并行操作(这应该将它们混合在 4 个线程之间)。从调试输出中,我可以看到确实产生了多个线程。每个线程都获得了一定的循环块,但线程似乎没有并行执行。线程 0 正在计算它的循环块,然后线程 1 计算它的块,依此类推。没有任何并行执行。执行时间就像 openmp 甚至没有激活一样。 EvalRPNInParallel 是执行 RPN 计算的成员函数。我没有在这个函数中使用任何锁、互斥锁和障碍。

double Foo::EvalRPNInParallel(int nOffset, int nThreadID) const
{
  double *Stack = &m_vStackBuffer[nThreadID * (m_vStackBuffer.size() / 4);
  for (const SToken *pTok = m_pRPN;  ; ++pTok)
  {
    switch (pTok->Cmd)
    {
      case  cmADD:  --sidx; Stack[sidx] += Stack[1+sidx]; continue;
      case  cmSUB:  --sidx; Stack[sidx] -= Stack[1+sidx]; continue;
      case  cmMUL:  --sidx; Stack[sidx] *= Stack[1+sidx]; continue;
      case  cmVAR:  Stack[++sidx] = *(pTok->Val.ptr + nOffset);  continue;
      // ...
      // ...
      // ...
      case  cmEND:  return Stack[m_nFinalResultIdx];  
    }
  }
}

奇怪的是,如果我故意用不必要的 for 循环减慢 EvalRPNInParallel 的速度,我确实看到了 EvalRPNInParallel 的并行执行,正如我所期望的那样。有谁知道为什么我没有看到使用 OpenMP 对她有任何好处?

[更新] 我还尝试了以下 openMP 构造,但都没有显示任何并行执行:

int nIterationsPerThread = nBulkSize/nMaxThreads;
#pragma omp parallel for private(nThreadID, j, k) shared(nMaxThreads, nIterationsPerThread) ordered
for (i=0; i<nMaxThreads; ++i)
{
  for (j=0; j<nIterationsPerThread; ++j)
  {
    nThreadID = omp_get_thread_num();
    k = i*nIterationsPerThread + j;
    printf("Thread %d Idx %d start",nThreadID, k);
    results[k] = ParseCmdCodeBulk(k, nThreadID);
    printf(" -- %d Idx %d end\n",nThreadID, k);
  }
}

使用部分:

#pragma omp parallel shared(nBulkSize) private(nThreadID, i)
{
  #pragma omp sections nowait
  {
    #pragma omp section
    for (i=0; i<(nBulkSize/2); ++i)
    {
      nThreadID = omp_get_thread_num();
      printf("Thread %d Idx %d start",nThreadID, i);
      results[i] = ParseCmdCodeBulk(i, nThreadID);
      printf(" -- %d Idx %d end\n",nThreadID, i);
    } // end of section

    #pragma omp section
    for (i=nBulkSize/2; i<nBulkSize; ++i)
    {
      nThreadID = omp_get_thread_num();
      printf("Thread %d Idx %d start",nThreadID, i);
      results[i] = ParseCmdCodeBulk(i, nThreadID);
      printf(" -- %d Idx %d end\n",nThreadID, i);
    } // end of section
  }
} // end of sections

【问题讨论】:

  • 创建/声明线程有一些开销 - 这不是微不足道的。如果您的 EvalRPN 函数足够快,那么它可能会在第二个线程有机会启动时结束。
  • msvc 的 printf 包含一个临界区。如果您使用 OutputDebugString 或删除它们会改变行为吗?
  • 循环被分成 4 个块,每个线程得到多个循环,而不仅仅是一个循环。线程不是在每个循环轮次中创建的,它们每个都执行 for 循环的大部分。我看到线程 1 计算 500 个循环,然后我看到线程 2 计算比线程 3 几百个循环。我永远不会看到两个线程同时工作。 printf 可能有一个锁,但这不会阻止两个 printf 之间的线程切换。手动设置块大小也不起作用。

标签: c++ visual-studio-2008 openmp


【解决方案1】:

经典的海森堡,观察线程会影响其行为。 printf() 函数很慢,肯定比你的表达式评估器慢得多。并且必须获取锁以防止字符串中的字符与其他线程请求的控制台输出混合。多个线程同时 进入 EvalRPNInParallel 函数的可能性并不大。顺便说一句,您无法通过诊断观察到。

并且通常的建议适用,只有在您测量了 3 次之后才优化您的代码 以找出瓶颈可能是什么。如果它需要超过几微秒,我会感到惊讶。在这种情况下你无法获胜,启动线程已经花费了更长的时间。您为找到瓶颈所做的相同测量也将告诉您线程是否让您领先。

【讨论】:

  • 在观察到不存在的性能提升后,我排除了添加 printfs 的可能性。评估本身只需要几微秒,但不是在每个循环轮次都创建线程,但它们会进行大量计算。
  • printf 不是这里的问题。如果我完全删除它并将调试信息写入数组,而不是我看到相同的结果:线程 1 计算它的块,然后是线程 2,然后是线程 3,最后是线程 4。但它们不能并行工作。每个线程计算数千个表达式。这需要几毫秒以上的时间。甚至将循环重新组织成一个双循环,对于核心数量,结果不会改变。
  • 您的代码 sn-p 没有任何证据表明这一点,我没有什么可以诊断您的问题。
  • 我真的很感激这一点,我用我已经尝试过的更多示例更新了原始问题。
  • 我也在看,但像@Hans 我看不出他们应该串行执行的任何理由,除非omp_get_num_threads() == 1(我假设它不是那么简单)鉴于其他地方没有锁等。 +1 提及优化和线程成本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-15
  • 1970-01-01
  • 1970-01-01
  • 2011-06-17
  • 1970-01-01
  • 2014-04-28
相关资源
最近更新 更多