C#线程池实现/性能峰值

C#线程池实现/性能峰值,c#,multithreading,threadpool,C#,Multithreading,Threadpool,为了加快C#中物理对象的处理速度,我决定将线性更新算法改为并行算法。我认为最好的方法是使用ThreadPool,因为它是为完成作业队列而构建的 当我第一次实现并行算法时,我为每个物理对象排队一个作业。请记住,单个作业完成得相当快(更新力、速度、位置,检查是否与任何周围对象的旧状态发生碰撞,以确保线程安全,等等)。然后,我将使用单个等待句柄等待所有作业完成,每次物理对象完成时,我都会使用一个互锁的整数递减(在命中零时,我会设置等待句柄)。等待是必需的,因为我需要做的下一个任务是更新所有对象 我注意

为了加快C#中物理对象的处理速度,我决定将线性更新算法改为并行算法。我认为最好的方法是使用ThreadPool,因为它是为完成作业队列而构建的

当我第一次实现并行算法时,我为每个物理对象排队一个作业。请记住,单个作业完成得相当快(更新力、速度、位置,检查是否与任何周围对象的旧状态发生碰撞,以确保线程安全,等等)。然后,我将使用单个等待句柄等待所有作业完成,每次物理对象完成时,我都会使用一个互锁的整数递减(在命中零时,我会设置等待句柄)。等待是必需的,因为我需要做的下一个任务是更新所有对象

我注意到的第一件事是表演太疯狂了。平均而言,线程池的速度似乎要快一点,但在性能上有巨大的峰值(每次更新大约10毫秒,随机跳到40-60毫秒)。我试图用ANTS来描述这一点,但是我无法理解为什么会出现尖峰

我的下一个方法是仍然使用ThreadPool,但是我将所有对象拆分为组。起初我只参加了8个小组,因为我的电脑核心都是这样的。演出很棒。它远远优于单线程方法,并且没有峰值(每次更新约6ms)

我唯一想到的是,如果一个任务比其他任务先完成,就会有一个空闲的内核。因此,我把工作增加到20个左右,甚至多达500个。正如我所料,它下降到5毫秒

因此,我的问题如下:

  • 为什么在我快速/大量调整工作规模时会出现峰值
  • 对于线程池是如何实现的,有什么见解可以帮助我理解如何最好地使用它吗

    • 使用线程是有代价的——你需要切换上下文,需要锁定(当线程试图获取新作业时,作业队列很可能被锁定)——这一切都是有代价的。与你的线程正在做的实际工作相比,这个价格通常很低,但是如果工作很快结束,这个价格就有意义了


      你的解决方案似乎是正确的。一个合理的经验法则是线程数是内核数的两倍。

      正如您自己可能预期的那样,峰值可能是由管理线程池并向其分配任务的代码引起的

      对于并行编程,有比“手动”在不同线程之间分配工作(即使使用线程池)更复杂的方法

      有关概述和不同选项,请参见示例。在您的情况下,“解决方案”可能非常简单:

      Parallel.ForEach(physicObjects, physicObject => Process(physicObject));
      
      对问题1的答复: 这是因为线程切换,线程切换(或操作系统概念中的上下文切换)是在每个线程之间切换所需的CPU时钟,多数情况下,多线程增加了程序和进程的速度,但当其进程如此小且快速时,上下文切换将比线程自身进程花费更多的时间,因此整个程序的吞吐量会降低,您可以在O.s的概念书中找到有关此方面的更多信息

      对问题2的答复:
      事实上,我对ThreadPool有一个全面的了解,我无法确切解释它的结构

      要了解有关线程池的更多信息,请从这里开始

      每个版本的.NET Framework都间接地利用ThreadPool添加了越来越多的功能。如前所述,添加到.NET4中,使代码更加可读和整洁。你也可以在这里了解更多


      在最基本的层面上,它所做的是:它创建20个线程,并将它们放入一个lits中。每次收到要执行异步的委托时,它都从列表中取出空闲线程并执行委托。如果没有找到可用的线程,则将其放入队列。每次deletegate执行完成时,它都会检查队列中是否有任何项目,如果有,它会偷看一个项目并在同一线程中执行。

      以下是我对您的两个问题的看法:

      我想从问题2(线程池如何工作)开始,因为它实际上是回答问题1的关键。线程池实现为(线程安全的)工作队列和一组工作线程(可以根据需要缩小或扩大)。当用户调用
      QueueUserWorkItem
      时,任务被放入工作队列。工人们不停地在队列中轮询,如果他们空闲,他们就开始工作。一旦他们成功地接受了一项任务,他们就会执行它,然后返回队列进行更多的工作(这非常重要!)。因此,工作是由工人按需完成的:当工人变得空闲时,他们需要做更多的工作

      综上所述,很容易看出问题1的答案是什么(为什么您会看到细粒度任务的性能差异):这是因为细粒度任务可以实现更大的负载平衡(这是一个非常理想的特性),也就是说,您的工人所做的工作或多或少是相同的,并且所有核心都是统一开发的。正如您所说,对于粗粒度任务分布,可能会有更长和更短的任务,因此一个或多个内核可能会落后,从而降低总体计算速度,而其他内核则什么也不做。小任务解决了问题。每个辅助线程一次执行一个小任务,然后返回执行更多任务。如果一个线程执行较短的任务,它将更频繁地进入队列,如果执行较长的任务,它将更少地进入队列,因此事情是平衡的

      最后,当作业过于细粒度时,并且考虑到池可能会扩大到超过1K个线程,当所有线程返回以承担更多工作(这种情况经常发生)时,队列上会出现非常高的争用,这可能会导致