-->

多核机器上的.NET操作的非线性缩放(Non-linear scaling of .NET oper

2019-08-08 03:08发布

我遇到的是一组在内存中的数据执行一些高度并行处理的.NET应用程序一个奇怪的行为。

当在多核心处理器(IntelCore2四Q6600的2.4GHz)运行它表现出非线性缩放作为多个线程踢掉来处理数据。

当作为在单个核心上的非多线程循环中运行,该方法能够完成大约每秒240万计算。 当四个线程运行你所期望的四倍的吞吐量 - 在每秒900万次计算,附近的地方 - 但很可惜,没有。 在实践中,只完成了约每秒4.1百万......从预期吞吐量相当有点短。

此外,该行为时,不管自己是否使用PLINQ,线程池,或四个明确创建线程。 很奇怪...

没有其他的计算机上运行使用的CPU时间,也没有参与任何计算锁或其他同步对象...这应该只是通过数据撕毁领先。 我已经证实了这一点(尽可能)通过perfmon的数据看,而进程运行...并没有报道线程争或垃圾收集活动。

我当时的理论:

  1. 所有的技术(线程上下文切换等)的开销压倒的计算
  2. 线程是没有得到分配到四个内核,并花一些时间等待同一个处理器内核..不知道如何测试这个理论...
  3. .NET CLR线程没有在预期的优先级运行还是有一些隐藏的内部开销。

下面是应该表现出同样的行为的代码有代表性的摘录:

    var evaluator = new LookupBasedEvaluator();

    // find all ten-vertex polygons that are a subset of the set of points
    var ssg = new SubsetGenerator<PolygonData>(Points.All, 10);

    const int TEST_SIZE = 10000000;  // evaluate the first 10 million records

    // materialize the data into memory...
    var polygons = ssg.AsParallel()
                      .Take(TEST_SIZE)
                      .Cast<PolygonData>()
                      .ToArray();

    var sw1 = Stopwatch.StartNew();
    // for loop completes in about 4.02 seconds... ~ 2.483 million/sec
    foreach( var polygon in polygons )
        evaluator.Evaluate(polygon);
    s1.Stop(); 
    Console.WriteLine( "Linear, single core loop: {0}", s1.ElapsedMilliseconds );

    // now attempt the same thing in parallel using Parallel.ForEach...
    // MS documentation indicates this internally uses a worker thread pool
    // completes in 2.61 seconds ... or ~ 3.831 million/sec
    var sw2 = Stopwatch.StartNew();
    Parallel.ForEach(polygons, p => evaluator.Evaluate(p));
    sw2.Stop();
    Console.WriteLine( "Parallel.ForEach() loop: {0}", s2.ElapsedMilliseconds );

    // now using PLINQ, er get slightly better results, but not by much
    // completes in 2.21 seconds ... or ~ 4.524 million/second
    var sw3 = Stopwatch.StartNew();
    polygons.AsParallel(Environment.ProcessorCount)
            .AsUnordered() // no sure this is necessary...
            .ForAll( h => evalautor.Evaluate(h) );
    sw3.Stop();
    Console.WriteLine( "PLINQ.AsParallel.ForAll: {0}", s3.EllapsedMilliseconds );

    // now using four explicit threads:
    // best, still short of expectations at 1.99 seconds = ~ 5 million/sec
    ParameterizedThreadStart tsd = delegate(object pset) { foreach (var p in (IEnumerable<Card[]>) pset) evaluator.Evaluate(p); };
     var t1 = new Thread(tsd);
     var t2 = new Thread(tsd);
     var t3 = new Thread(tsd);
     var t4 = new Thread(tsd);

     var sw4 = Stopwatch.StartNew(); 
     t1.Start(hands);
     t2.Start(hands);
     t3.Start(hands);
     t4.Start(hands);
     t1.Join();
     t2.Join();
     t3.Join();
     t4.Join();
     sw.Stop();
     Console.WriteLine( "Four Explicit Threads: {0}", s4.EllapsedMilliseconds );

Answer 1:

看看这篇文章: http://blogs.msdn.com/pfxteam/archive/2008/08/12/8849984.aspx

具体而言,限制内存分配在并行区域,并仔细检查写入,以确保它们不会发生在接近其他线程读取或写入内存位置。



Answer 2:

所以,我终于想通了,问题是什么 - 我认为这将是与SO社区分享它有用。

具有非线性性能的整个问题是内部的单线的结果Evaluate()方法:

var coordMatrix = new long[100];

由于Evaluate()被调用数百万次,该存储器分配是存在数百万次。 因为它发生时,CLR分配存储器时在内部执行一些线程间同步 - 否则在多个线程分配可能无意中重叠。 从方法本地实例的阵列改变到仅分配一次一个类的实例(但然后在方法本地环路初始化)消除了可扩展性问题。

通常情况下,它是一个反模式来创建类级部件对于被仅单个方法的范围内使用(和有意义的)的变量。 但是,在这种情况下,我需要尽可能大的可扩展性,我将住在一起(和文件),这种优化。

后记:我做了这个更改后,并发进程才得以实现1220万次计算/秒。

PS荣誉伊戈尔奥斯特洛夫斯基对他的密切联系到MSDN博客这帮助我识别和诊断问题。



Answer 3:

非线性缩放是与在具有顺序算法比较的并行算法可以预料的,因为在并行一些固有的开销。 (理想情况下,当然,你希望得到尽可能接近你可以)。

此外,通常会有你需要在一个并行算法,你不需要在顺序算法照顾某些事情。 除了同步(可真正拖垮你的工作),还有一些其他的东西,可能发生:

  • CPU和OS不能将所有的它的时间投入到你的应用程序。 因此,它需要做的上下文切换时不时让其他进程得到一些工作要做。 如果你只使用一个单一的核心,它是不太可能,你的处理切换出来,因为它有另外三个内核选择。 请注意,即使你可能会认为没有其他正在运行,操作系统或某些服务仍然可以进行一些后台工作。
  • 如果每个线程访问的是大量的数据,这些数据是不是线程之间的共,你将最有可能不能够将所有的这种存储在CPU缓存。 这意味着大量的更多的存储器存取是必需的,它是(相对)慢。

据我所知,你目前明确的方法使用线程之间共享的迭代器。 这是一个好解决方案,如果该处理在整个阵列中变化似地,但有可能是同步开销,以防止元件从被跳过(检索所述当前元素和所述内部指针移动到下一个元素需要是一个原子操作,以防止跳过的元素)。

因此,它可能是一个更好的主意到阵列划分,假设每个元素的处理时间预计为大致相等而与元件的位置的。 假设你有10万条记录,这意味着告诉线程1对元素工作0至2499999,线2个工程上的元素250万〜4,999,999等,您可以指定每个线程的ID,并用它来计算出实际的范围。

另一个小的改进是让主线程充当计算一个线程。 但是,如果我没有记错,这是一个次要的事情。



Answer 4:

我当然不会期望的线性关系,但我还以为你会看到比这更大的增益。 我假设的CPU使用率刷爆了所有的核心。 只是一对夫妇的把我的头顶部的想法。

  • 您是否使用需要同步的任何共享的数据结构(显式或隐式)?
  • 您是否尝试过分析或记录性能计数器,以确定的瓶颈是什么? 你可以给任何更多的线索。

编辑:对不起,我刚才注意到你已经解决我的两个点。



文章来源: Non-linear scaling of .NET operations on multi-core machine