ThreadPool回调紧密循环 - 100%CPU

时间:2012-04-13 14:58:21

标签: c# cpu threadpool

我的算法中有一个方法,它在非常大的数据集上运行非常紧密的循环。我最初写它是单线程,这很好,但它花了很长时间。我现在想要加速它,所以我现在使用ThreadPool来并行化工作。问题是这导致我的CPU使用率达到95-100%,这是我所期望的。但是,我的表现有了很大的提升,但我认为如果我可以减少所有的上下文切换,我可以做得更好。这也导致我的其他程序有点滞后,因为它们必须与线程争用CPU资源。

我的问题是我应该怎么做呢?我唯一能想到的是限制一次运行的线程数,但这可能会使我的算法变慢,因为一次只能运行几个线程。我不想在我的线程中添加睡眠,因为我只需要算法尽快运行完成。

编辑:有几个人提到过使用TPL。我认为这是一个好主意,但不幸的是我忘了提到我使用.NET 3.5,因为父应用程序尚未发布使用.NET 4的版本。

2 个答案:

答案 0 :(得分:6)

这都是关于资源管理的。您的程序目前占用了所有资源,因此其他程序可以减少对它们的访问。您需要平衡“我只需要算法尽快运行到完成”部分与“这也会导致我的其他程序有点滞后,因为他们必须对抗CPU资源的线程”。它们是相互排斥的;您无法在特定计算机上以尽可能快的速度运行应用程序,也可以使其他应用程序完全响应。 CPU在任何时间段都可以做多少就是一个限制。

就效率提升而言,您可以做一些事情:

  • 不要将ThreadPool用于超优化的线程算法。 ThreadPool非常适合简单的“关闭并执行此操作并让我知道您已完成”操作。但是,如果您希望进行优化,则可以避免使用ThreadPool添加额外级别的线程调度(在CPU和OS固有的开销之上)所固有的开销。您还可以对ThreadPool中的线程进行更有限的控制,这意味着无法为各个线程分配处理器关联(负载平衡)和优先级(给予线程更多或更少时间)等优化。尝试创建简单的线程,或者查看具有多种策略的TPL,以完成多项工作(并非所有这些都需要首先进行线程化)。

  • 是的,您希望能够“节制”线程数。这可以通过减少程序对它的需求来允许其他程序占用一些CPU时间,但正如我所说,多线程中也存在固有的开销。经验法则是,如果CPU的主动运行线程数超过两倍,因为它具有“执行单元”(这些是CPU芯片上的物理内核,而“逻辑处理器”,如分裂一个内核的超线程技术)到两个),然后操作系统将花费更多的时间来调度线程并在它们之间切换(“缓存 - 颠簸”),而不是花在实际运行线程上。更一般地说,有一个收益递减法则,它将演变为“规模​​不经济”;最终,添加另一个线程会导致程序运行速度比没有使用该线程时慢。是的,ThreadPool为您处理最大线程,但这可能是您在自己的算法中实现自己的各种功能中最简单的。

  • 确保每个线程的工作都已优化。寻找天真或低效的算法(我称之为“ O (我的上帝) - 复杂性”)并简化它们。大多数操作的效率有一个下限(它因操作类型而异),“过早优化是万恶之源”(不要以牺牲代码实际工作为代价来优化性能),但是了解在多线程环境中,运行一次算法对算法效率的任何增益都会乘以运行它的次数,因此确保并行操作是有效的双重奖励。 / p>

答案 1 :(得分:2)

如果您可以将主应用程序重写为foreach上的IEnumerable循环,则可以使用PLINQ来并行化循环。您可以使用WithDegreeOfParallelism来控制应用程序将使用的核心数。您可以通过不使用计算机上的所有核心来防止您遇到的某些“延迟”。此外,您不必处理如何跨线程对循环进行分区以避免不必要的资源争用。 PLINQ为您做到了这一切。

假设你有这个非常简单的单线程循环:

var arrayOfStuff = new[] { ... };
for (var i = 0; i < arrayOfStuff.Length; ++i)
  DoSomething(arrayOfStuff[i]);

如果排序无关紧要,您可以使用PLINQ使用少于可用的核心并行化它:

var cores = Math.Max(1, Environment.ProcessorCount - 1);
arrayOfStuff.AsParallel().WithDegreeOfParallelism(cores).ForAll(DoSomething);

即使您的主循环更复杂,您也可以将其重写为迭代器块,然后可以并行化:

IEnumerable<Stuff> GetStuff() {
  for ( ... very complex looping ... ) {
    ...
    yield return stuff;
  }
}