多线程程序不使用100%CPU用于昂贵的IEnumebles

问题描述 投票:-2回答:1

具有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()GenerateLongString()内。
  • 手动运行多个Task.Factory.StartNew()vs Thread.Start()时会出现相同的行为
  • 在Win 10和Windows Server 2012上观察到相同的行为
  • 在真实机器和虚拟机上观察到相同的行为
  • Release vs. Debug无关紧要。
  • .Net版测试:4.7.2

完整代码:

class Program
{
    const int DATA_SIZE = 10000;
    const int IENUMERABLE_COUNT = 10000;

    static void Main(string[] args)
    {
        // initialisation - takes milliseconds
        IEnumerable<string>[] iEnumerablesArray = GenerateArrayOfIEnumerables();

        Console.WriteLine("Initialized");

        List<string> result = null;

        // =================
        // THE PROBLEM LINE:
        // =================
        // CPU usage of next line:
        //    - 40 % on 4 virtual cores processor (2 physical)
        //    - 10 - 15 % on 12 virtual cores processor
        Parallel.For(
            0, 
            int.MaxValue, 
            (i) => result = iEnumerablesArray.Aggregate(Enumerable.Concat).ToList());

        // just to be sure that Release mode would not omit some lines:
        Console.WriteLine(result);
    }

    static IEnumerable<string>[] GenerateArrayOfIEnumerables()
    {
        return Enumerable
              .Range(0, IENUMERABLE_COUNT)
              .Select(_ => Enumerable.Range(0, 1).Select(__ => GenerateLongString()))
              .ToArray();
    }

    static string GenerateLongString()
    {
        return string.Concat(Enumerable.Range(0, DATA_SIZE).Select(_ => "string_part"));
    }
}
c# .net multithreading ienumerable cpu-usage
1个回答
2
投票

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

也就是说,还有另一种加快速度的方法。默认情况下,在桌面上,GC配置为使用计算机上的有限资源(以避免减慢其他应用程序的速度)。如果你想充分利用可用的资源,那么你可以activate server GC。此模式假定您的应用程序是计算机上运行的最重要的事情。它将提供显着的性能提升,但使用更多的CPU和内存。

© www.soinside.com 2019 - 2024. All rights reserved.