簡體   English   中英

在多核機器上進行.NET操作的非線性擴展

[英]Non-linear scaling of .NET operations on multi-core machine

我在.NET應用程序中遇到了一種奇怪的行為,它對一組內存數據執行一些高度並行的處理。

當在多核處理器(IntelCore2 Quad 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 );

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

具體來說,限制並行區域中的內存分配,並仔細檢查寫入以確保它們不會發生在其他線程讀取或寫入的內存位置附近。

所以我終於弄清楚問題是什么 - 我認為與SO社區分享它會很有用。

非線性性能的整個問題是Evaluate()方法中單行的結果:

var coordMatrix = new long[100];

由於Evaluate()被調用數百萬次,因此這種內存分配發生了數百萬次。 碰巧,CLR在分配內存時在內部執行一些線程間同步 - 否則在多個線程上的分配可能會無意中重疊。 將數組從方法本地實例更改為僅分配一次的類實例(但隨后在方法本地循環中初始化)消除了可伸縮性問題。

通常,為一個僅在單個方法范圍內使用(且有意義)的變量創建類級別成員是反模式。 但在這種情況下,由於我需要最大可能的可擴展性,我將繼續(並記錄)此優化。

結語:在我做了這個改變之后,並發進程能夠達到1220萬次計算/秒。

PS感謝Igor Ostrovsky與MSDN博客的密切聯系,幫助我識別和診斷問題。

與順序算法相比,使用並行算法可以預期非線性縮放,因為並行化存在一些固有的開銷。 (理想情況下,當然,你想盡可能接近。)

此外,在並行算法中通常需要處理某些事情,而在順序算法中則不需要這些算法。 除了同步(這可能會讓您的工作陷入困境)之外,還有其他一些事情可能發生:

  • CPU和OS無法將所有時間用於您的應用程序。 因此,它需要不時地進行上下文切換,讓其他進程完成一些工作。 當您只使用單個核心時,您的流程不太可能被切換出來,因為它有三個其他核心可供選擇。 請注意,即使您可能認為沒有其他任何內容正在運行,操作系統或某些服務仍可能正在執行某些后台工作。
  • 如果每個線程都在訪問大量數據,並且這些數據在線程之間不常見,那么很可能無法將所有這些數據存儲在CPU緩存中。 這意味着需要更多的內存訪問,這是(相對)慢。

據我所知,您當前的顯式方法使用線程之間的共享迭代器。 如果處理在整個陣列中變化很大,這是一個好的解決方案,但是可能存在同步開銷以防止跳過元素(檢索當前元素並將內部指針移動到下一個元素需要是一個原子操作來防止跳過一個元素)。

因此,對陣列進行分區可能是一個更好的想法,假設每個元素的處理時間預計大致相等,而不管元素的位置如何。 假設您有1000萬條記錄,這意味着告訴線程1處理元素0到2,499,999,線程2處理元素2,500,000到4,999,999等。您可以為每個線程分配一個ID並使用它來計算實際范圍。

另一個小的改進是讓主線程作為計算的線程之一。 但是,如果我沒記錯的話,這是一件非常小的事情。

我當然不會期待線性關系,但我認為你會看到比這更大的收益。 我假設所有內核的CPU使用率最大化。 我只想了幾個想法。

  • 您是否正在使用需要同步的任何共享數據結構(顯式或隱式)?
  • 您是否嘗試過分析或記錄性能計數器以確定瓶頸在哪里? 你能不能提供更多線索?

編輯:對不起,我剛注意到你已經解決了我的兩點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM