2012-08-27 138 views
9

只是一個快速的筆記,這不是作業。我只是試圖刷新我的算法。我與歸併玩弄在C#中,我已經寫了可以進行排序基於泛型遞歸方法:C#合併排序性能

class SortAlgorithms 
{ 

    public T[] MergeSort<T> (T[] unsortedArray) where T : System.IComparable<T> 
    { 
     T[] left, right; 
     int middle = unsortedArray.Length/2; 

     left = new T[middle]; 
     right = new T[unsortedArray.Length - middle]; 

     if (unsortedArray.Length <= 1) 
      return unsortedArray; 

     for (int i = 0; i < middle; i++) 
     { 
      left[i] = unsortedArray[i]; 
     } 

     for (int i = middle; i < unsortedArray.Length; i++) 
     { 
      right[i - middle] = unsortedArray[i]; 
     } 

     left = MergeSort(left); 

     right = MergeSort(right); 


     return Merge<T>(left, right); 
    } 

    private T[] Merge<T> (T[] left, T[] right) where T : System.IComparable<T> 
    { 
     T[] result = new T[left.Length + right.Length]; 

     int currentElement = 0; 

     while (left.Length > 0 || right.Length > 0) 
     { 
      if (left.Length > 0 && right.Length > 0) 
      { 
       if (left[0].CompareTo(right[0]) < 0) 
       { 
        result[currentElement] = left[0]; 
        left = left.Skip(1).ToArray(); 
        currentElement++; 
       } 
       else 
       { 
        result[currentElement] = right[0]; 
        right = right.Skip(1).ToArray(); 
        currentElement++; 
       } 
      } 
      else if (left.Length > 0) 
      { 
       result[currentElement] = left[0]; 
       left = left.Skip(1).ToArray(); 
       currentElement++; 
      } 
      else if (right.Length > 0) 
      { 
       result[currentElement] = right[0]; 
       right = right.Skip(1).ToArray(); 
       currentElement++; 
      } 
     } 

     return result; 
    } 
} 

這工作,但它是痛苦的緩慢。我已經使用System.Diagnostic.StopWatch檢查性能與Array.Sort(使用QuickSort算法)來比較我的MergeSort和差異是如此重要,我想知道如果也許我實現這個錯誤。任何意見?

+1

你看過Jons文章嗎? http://msmvps.com/blogs/jon_skeet/archive/2011/01/06/reimplementing-linq-to-objects-part-26c-optimizing-orderedenumerable.aspx –

+0

你有沒有試過同樣的實現,但沒有泛型? – pfries

+0

偉大的答案傢伙。對不起,花了這麼長時間纔回應,我一直在重寫代碼,結果代碼看起來幾乎完全像Rafe所建議的。速度快得多,但仍然比原生Array.Sort慢得多。仍在玩一下。 – hobeau

回答

12

我不是C#程序員,但問題可能是使用像這樣的語句嗎?

left = left.Skip(1).ToArray(); 

這可能會強制深層複製底層數組。如果是這樣,則將從O(n)到O(n )的合併性能降低,立即將產生的合併排序的性能從O(n log n)降到O(n )。

(這是因爲從

T(1)= O(1)

T(n)的≤ 2T(N/2)+ O(n)的

復發變化

具有溶液T(N)= O(N log n)的,到

T(1)= O(1)

T(n)的≤ 2T(N/2)+ O(N )

具有溶液T(N)= O(N )。)

+1

它沒有實現深層複製,但它確實會導致創建一個新數組(使用淺拷貝)。不知道它是如何改變邊界的,但它仍然不理想。 – 2012-08-27 20:19:50

+1

@ pst-如果這將創建一個新陣列並淺層複製元素,這也會導致性能下降,因爲淺拷貝仍然需要花費O(n)時間。 – templatetypedef

+0

感謝您的澄清,我不確定這是否是深拷貝的一個方面。 – 2012-08-27 20:27:16

2

您一直以中間數組的形式分配內存。考慮重用原始數組的方向。

1

作爲另外兩個答案曾經說過,你正在創建新的陣列,花費大量的時間和內存(我猜,大部分時間和幾乎所有的內存都在使用)。

在此我再次補充說,其他所有相等的遞歸往往比迭代更慢,並使用更多的堆棧空間(甚至可能導致溢出時有足夠大的問題,迭代不會)。

但是,合併排序很好地適用於多線程方法,因爲您可以讓不同的線程處理第一批分區的不同部分。

因此,如果它是我玩這個,我的下兩個實驗將是:

  1. 對於分區的第一位,而不是調用MergeSort遞歸,我會推出一個新的線程,直到這是我每個內核運行一個線程的時間(無論是在物理核心還是在超線程的情況下,我都應該這麼做),這本身就是我試驗的東西。
  2. 這樣做,我會嘗試重寫遞歸方法做同樣的事情,無需遞歸調用。

ToArray()此事被處理,看到一個多線程的辦法,首先拆分工作核心的最佳數量中,再有每個內核怎麼辦的工作反覆,可能是相當有趣的確實。

+1

與其處理是否產生一個新的線程,你可以創建一個新的Task,讓它全部進入一個線程池。線程池的大小可能大約爲物理處理器的數量。 – Servy

+1

根據我的經驗,在iCore 5/7系列中,合併排序(儘管在Windows上的JVM上)「效果最好」,每個核心的線程數大約爲1.5-2 * max *(不要低估進程竊取;-)。另外,線程不應該用於小的'n'(葉子和邊緣分支),並且切換到邊緣的非合併排序是一種「通用優化」。 – 2012-08-27 20:25:55

+0

@pst「per core」核心還是超線程虛擬內核? –

0

首先,這裏是一個簡化的解決方案鏈接到一個類似的問題:Java mergesort, should the "merge" step be done with queues or arrays?

你的解決方案是緩慢的,因爲你反覆分配新的子陣。內存分配比大多數其他操作更貴(您有分配成本,收集成本和緩存局部性丟失)。通常情況下這不是問題,但是如果你想編寫嚴格的排序程序,那麼它很重要。對於合併排序,您只需要一個目標數組和一個臨時數組。

分叉的線程並行化仍然比這個數量級更昂貴。所以除非你有大量的數據要排序,否則不要分叉。

正如我在上面的答案中提到的,加快合併排序的一種方法是利用輸入數組中的現有順序。