多线程程序不将100%CPU用于昂贵的IEnumerables

时间:2018-09-02 11:25:47

标签: c# .net multithreading ienumerable cpu-usage

具有 IEnumerables 的多线程,并行评估几次,评估起来很昂贵,不使用100%CPU 。示例是与Concat()结合使用的Aggregate()函数:

// Initialisation.
// Each IEnumerable<string> is made so that it takes time to evaluate it
// everytime when it is accessed.
IEnumerable<string>[] iEnumerablesArray = ...  

// The line of the question (using less than 100% CPU):
Parallel.For(0, 1000000, _ => iEnumerablesArray.Aggregate(Enumerable.Concat).ToList());

问题:为什么对IEnumerables进行多次并行评估的并行代码不使用100%CPU??该代码不使用锁或等待,因此这种行为是意外的。文章的末尾有一个完整的代码来模拟这一点。


注释和修改:

  • 有趣的事实:如果代码
    Enumerable.Range(0, 1).Select(__ => GenerateLongString()) 完整代码最后的
    更改为
    Enumerable.Range(0, 1).Select(__ => GenerateLongString()).ToArray().AsEnumerable()
    然后初始化需要几秒钟的时间,然后CPU使用率达到100%(没有问题)
  • 有趣的事实2:(来自注释)如果使方法GenerateLongString()在GC上不那么繁重,而在CPU上更繁琐,则CPU变为100%。因此原因与该方法的实现有关。但是,有趣的是,如果在没有IEnumerable的情况下调用GenerateLongString()的当前形式,则CPU也会达到100%:
    Parallel.For(0, int.MaxValue, _ => GenerateLongString());
    所以GenerateLongString()的繁重并不是这里的唯一问题。
  • 事实3: (摘自评论)建议concurrency visualiser透露,线程大部分时间都花在了网上
    clr.dll!WKS::gc_heap::wait_for_gc_done
    等待GC完成。这是在string.Concat()的{​​{1}}内部发生的。
  • 手动运行多个Task.Factory.StartNew()或Thread.Start()时,观察到相同的行为
  • 在Win 10和Windows Server 2012上观察到相同的行为
  • 在真实计算机和虚拟机上观察到相同的行为
  • 发布与调试无关紧要。
  • .Net测试版:4.7.2

完整代码:

GenerateLongString()

1 个答案:

答案 0 :(得分:2)

您的线程在clr.dll!WKS::gc_heap::wait_for_gc_done上被阻塞的事实表明,垃圾收集器是应用程序的瓶颈。您应该尽可能地限制程序中堆分配的数量,以减轻对gc的压力。

也就是说,还有另一种加快处理速度的方法。默认情况下,在台式机上,GC配置为使用计算机上有限的资源(以避免降低其他应用程序的速度)。如果要充分利用可用资源,则可以activate server GC。此模式假定您的应用程序是计算机上运行的最重要的东西。它将显着提高性能,但会使用更多的CPU和内存。