2010-08-26 54 views
10

我一直在測試System.Threading.Parallel與線程的性能,我很驚訝地看到並行需要更長的時間才能完成任務,而不是線程。我相信這是由於我對Parallel的知識有限,我剛剛開始閱讀。C#並行VS.線程代碼的性能

我想我會分享一些片段,如果有人可以指出我並行代碼運行速度較慢與線程代碼。還嘗試運行相同的比較來查找素數,並發現並行代碼比線程代碼晚得多。

public class ThreadFactory 
{ 
    int workersCount; 
    private List<Thread> threads = new List<Thread>(); 

    public ThreadFactory(int threadCount, int workCount, Action<int, int, string> action) 
    { 
     workersCount = threadCount; 

     int totalWorkLoad = workCount; 
     int workLoad = totalWorkLoad/workersCount; 
     int extraLoad = totalWorkLoad % workersCount; 

     for (int i = 0; i < workersCount; i++) 
     { 
      int min, max; 
      if (i < (workersCount - 1)) 
      { 
       min = (i * workLoad); 
       max = ((i * workLoad) + workLoad - 1); 
      } 
      else 
      { 
       min = (i * workLoad); 
       max = (i * workLoad) + (workLoad - 1 + extraLoad); 
      } 
      string name = "Working Thread#" + i; 

      Thread worker = new Thread(() => { action(min, max, name); }); 
      worker.Name = name; 
      threads.Add(worker); 
     } 
    } 

    public void StartWorking() 
    { 
     foreach (Thread thread in threads) 
     { 
      thread.Start(); 
     } 

     foreach (Thread thread in threads) 
     { 
      thread.Join(); 
     } 
    } 
} 

下面是程序:

Stopwatch watch = new Stopwatch(); 
watch.Start(); 
int path = 1; 

List<int> numbers = new List<int>(Enumerable.Range(0, 10000)); 

if (path == 1) 
{ 
    Parallel.ForEach(numbers, x => 
    { 
     Console.WriteLine(x); 
     Thread.Sleep(1); 

    }); 
} 
else 
{ 
    ThreadFactory workers = new ThreadFactory(10, numbers.Count, (min, max, text) => { 

     for (int i = min; i <= max; i++) 
     { 
      Console.WriteLine(numbers[i]); 
      Thread.Sleep(1); 
     } 
    }); 

    workers.StartWorking(); 
} 

watch.Stop(); 
Console.WriteLine(watch.Elapsed.TotalSeconds.ToString()); 

Console.ReadLine(); 

更新:

以鎖定考慮:我嘗試下面的代碼片段。同樣的結果,Parallel似乎要慢得多。

path = 1; cieling = 10000000;

List<int> numbers = new List<int>(); 

    if (path == 1) 
    { 
     Parallel.For(0, cieling, x => 
     { 
      lock (numbers) 
      { 
       numbers.Add(x);  
      } 

     }); 
    } 

    else 
    { 
     ThreadFactory workers = new ThreadFactory(10, cieling, (min, max, text) => 
     { 

      for (int i = min; i <= max; i++) 
      { 
       lock (numbers) 
       { 
        numbers.Add(i);  
       }      

      } 
     }); 

     workers.StartWorking(); 
    } 

更新2: 只是一個快速更新,我的機器有四核處理器。所以並行有4個內核可用。

+1

您不應該鎖定ForEach,它會在內部執行此操作。但是使用ReaderWriterLockSlim將使其再次變快;) – 2010-08-26 07:44:45

+0

將您的ThreadFactory設置爲2個線程,並將Parallel.For上的最大併發設置爲2,擺脫Console.WriteLine並執行更合適的操作。現在他們如何比較?嘗試3和3; 4和4; ...在某些時候,Parallel.ForEach會決定分配足夠多的線程,並且分配的分配量不會超過您所說的最大值,但至少在這一點上,您將使用*相同數量的線程比較時間。 – 2010-08-26 08:00:58

+0

@Hightechrider:好吧,就像我在我的問題中提到的那樣,我在測試中發現了質數,這個處理器相當密集,整個過程顯示100%的活動,並且發現ThreadFactory運行速度更快。試試看,我甚至嘗試設置線程數爲2,3等相同的結果。 – ace 2010-08-26 17:58:33

回答

3

指的一個blog post勵科普塞JR:

Parallel.ForEach是有點複雜,但是。當使用通用的IEnumerable時,處理所需的項目數量不是事先知道的,必須在運行時發現。另外,由於我們沒有直接訪問每個元素,調度程序必須枚舉集合來處理它。 由於IEnumerable不是線程安全的,因此它必須在枚舉時鎖定元素,爲每個塊創建臨時集合以進行處理,並將其計劃到

鎖定和複製可能會使Parallel.ForEach花費更長的時間。此外,ForEach的分區和調度程序可能會影響併產生開銷。我測試了你的代碼並增加了每個任務的睡眠時間,然後結果更接近,但ForEach仍然較慢。

[編輯 - 更多的研究]

添加以下到執行循環:

if (Thread.CurrentThread.ManagedThreadId > maxThreadId) 
    maxThreadId = Thread.CurrentThread.ManagedThreadId; 

什麼這說明我的機器上是它使用較少用foreach 10個線程,比另一個與當前的設置。如果你想從ForEach中獲得更多的線程,你將不得不使用ParallelOptions和Scheduler。

Does Parallel.ForEach limits the number of active threads?

+0

Intresting ...讓我試着在啓用了鎖定的列表上插入比較。日Thnx。 – ace 2010-08-26 07:03:46

+0

看到您的更新...並修改了我的答案了一下。 – 2010-08-26 07:23:34

+0

再次編輯:)它歸結爲正在使用的線程數。 – 2010-08-26 07:57:14

3

我想我可以回答你的問題。首先,你沒有寫出你的系統有多少核心。如果你正在運行一個雙核心的話,那麼當你在線程例子中使用10個線程時,只有4個線程可以使用Parallel.For工作。由於您正在運行的任務(打印+短暫睡眠)對於線程來說是一個非常短的任務,線程開銷與任務相比非常大,所以更多的線程會更好地工作,我幾乎可以肯定,如果您在沒有線程的情況下編寫相同的代碼它會工作得更快。

兩種方法的工作方式都差不多,但如果事先創建所有線程,則將分配保存爲Parallel.For,並使用添加一些移動開銷的任務池。

+0

+1:問題是總蘋果與橙子的比較,因爲它使用不同數量的線程。 Console.WriteLine對於測試用例來說也是一個糟糕的選擇。 – 2010-08-26 07:57:33

+0

我有一個四核心。 @Hightechrider我甚至通過查找素數來測試它。相同的結果。請嘗試我的代碼示例並查看結果。 – ace 2010-08-26 18:02:54

+0

再次提醒 - Parallel-s的唯一承諾是做「比手工更好」的排程。沒有它,除非作爲可能的便利語法,否則它並沒有多大用處。 – ZXX 2010-08-27 00:57:51

0

這個比較對於Threading.Parallel來說並不是很公平。你告訴你的自定義線程池它需要10個線程。 Threading.Parallel不知道它需要多少線程,所以它會在運行時考慮到當前的CPU負載等因素。由於測試中的迭代次數足夠小,因此可以減少此線程的數量。提供Threading.Parallel同樣暗示將使它運行得更快:

 

int workerThreads; 
int completionPortThreads; 
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads); 
ThreadPool.SetMinThreads(10, completionPortThreads); 
 
+0

Thnx ...我會嘗試一下,看看它是否有所作爲。 – ace 2010-08-26 18:05:09

+0

不要忘記,Parallel-s的唯一承諾是做「比手工更好」的調度。對於空情況來說太棒了 - 再來一個勺子:-) – ZXX 2010-08-27 00:52:01

0

這是合乎邏輯的:-)

這將是歷史上的第一次添加的代碼一個(或兩個)層改進的性能。當您使用便利圖書館時,您應該期望付出代價。順便說一句,你還沒有發佈數字。必須發佈結果:-)

爲了讓事情有點更失敗(或偏見:-)爲Parallel-s,將列表轉換爲數組。

然後爲了讓他們完全不公平,請自行拆分工作,製作一個只包含10個項目的數組,並將完全勺子饋送動作分配給並行。你當然在做Parallel-s承諾爲你做的工作,但這肯定是一個有趣的數字:-)

順便說一句我剛剛讀過裏德的博客。在這個問題中使用的分區是他稱之爲最簡單和天真的分區。 這確實是一個很好的消除測試。您仍然需要檢查零工作案例,以瞭解它是否完全被洗淨。

+0

哈哈......好吧,如果我正在做所有的工作,那麼它會擊敗點儀式? – ace 2010-08-26 18:07:12

+0

它給你的測量告訴你,如果Parallel-s在他們沒有做任何工作時至少可以加快速度 - 把它想象成一個排除問題:-) 至少,那部分工作得很好,然後他們可能會可作爲便利功能。如果沒有 - 你很早就發現了哪些dll可以避免。 – ZXX 2010-08-27 00:42:05

+0

不知道如果我準備放棄它,那裏應該有更好的優化,我在並行調用時缺少的設置。 – ace 2010-08-27 01:26:41