使用提升条件互斥体时性能不佳

poor performance with boost conditional mutex

本文关键字:性能 条件      更新时间:2023-10-16

我是使用conditional_variables的新手,所以我可以很容易地在这里做一些愚蠢的事情,但是当我使用boost线程而不是直接调用函数时,我得到了一些奇怪的性能。 如果我将 func 上创建提升线程的行更改为直接调用 func,则代码运行速度会更快地运行多个订单。 我尝试使用源代码锻造的提升线程池软件,这没有什么区别......

这是代码:

#include <boost/thread.hpp>

using namespace boost;
condition_variable cond;
mutex conditionalMutex;
int numThreadsCompleted = 0;
int numActiveThreads = 0;
void func()
{
  {
    lock_guard<mutex> lock(conditionalMutex);
    --numActiveThreads;
    numThreadsCompleted++;
  }
  cond.notify_one();
};

int main()
{
  int i=0;
  while (i < 100000)
    {
      if (numActiveThreads == 0)
        {
          ++numActiveThreads;
          thread thd(func);
          //Replace above with a direct call to func for several orders of magnitude
          //performance increase...
          ++i;
        }
      else
        {
          unique_lock<mutex> lock(conditionalMutex);
          while (numThreadsCompleted == 0)
            {
              cond.wait(lock);
            }
          numThreadsCompleted--;
        }
    }
  return 0;
}

性能一定比直接调用函数差很多。启动一个线程,然后等待该线程结束。即使将启动线程的开销减少到零,也可以与该线程通信。而且您将至少有一个上下文切换,并且由于您的func()基本上什么都不做,因此开销成为重要因素。在 func() 中添加更多有效负载,比率将发生变化。如果必须做的事情很少,只需在发现这件事的线程上做。

顺便说一句:您有一个竞争条件,因为您在没有锁定互斥锁的情况下写入 numActiveThreads。上面的代码归结为:

int main()
{
    int i=0;
    while (i < 100000)
    {
        thread thd(func);
        thd.join();
        ++i;
    }
    return 0;
}

而且真的没有理由为什么这应该比:

int main()
{
    int i=0;
    while (i < 100000)
    {
        func();
        ++i;
    }
    return 0;
}

您正在创建和销毁线程,这些线程通常作为一些较低级别的操作系统构造实现,通常是某种轻量级进程。 这种创造和破坏可能代价高昂。

最后,你基本上在做

  1. 创建线程
  2. 等待线程退出

一遍又一遍。 这意味着创建/销毁,你每次都在这样做,所以成本会加起来。

除了创建和销毁线程的开销之外,分支预测还可能导致性能差异。

如果没有线程,if 语句始终为 true,因为numActiveThreads将在每次循环迭代的开始和结束时0

while (i < 100000)
{
  if (numActiveThreads == 0) // branch always taken
  {
    ++numActiveThreads; // numActiveThreads = 1
    func();             // when this returns, numActiveThreads = 0
    ++i;                
  }
}

这导致:

  • 分支预测永不失败。
  • 没有线程创建/销毁的开销。
  • 没有时间被阻塞等待获取conditionalMutex

使用线程处理时,numActiveThreads可能会也可能不会在顺序迭代中0。 在我测试的大多数机器上,观察到了短的可预测模式,每次迭代时 if 语句和 else 语句之间的分支交替出现。 但是,有时 if 语句是在顺序迭代中选择的。 因此,时间可能会浪费在以下方面:

  • 分支预测失败。
  • 线程的创建和销毁。 如果创建和销毁是并发的,则同步可能会在基础线程库中发生。
  • 已阻止等待获取conditionMutex或等待cond