更改記憶體分配策略改善歸併排序效率

2021-06-29 11:25:48 字數 2067 閱讀 7738

歸併排序是一種相當穩健的排序演算法,無論何種輸入序列,其期望時間複雜度和最壞時間複雜度都是θ(nlogn),這已經達到了基於比較排序演算法的漸進下界。

因此歸併排序時常會用於對可能導致quicksort退化的序列排序。

歸併排序是典型的分治演算法,乙個最常見的實現如下:

void mergesort(int a, const

int low, const

int high)

return ;

}void merge(int a, const

int lowfirst, const

int highfirst, const

int highlast)

while (l < highfirst)

while (r <= highlast)

p = pbuffer;

for (int i = lowfirst; i <= highlast;)

delete pbuffer;

}

但是在實踐中,歸併排序花費的時間往往超過預期,對於普通的序列而言,所花費的時間甚至遠遠超過quicksort。

究其原因,和歸併排序的記憶體策略有關(不斷地分配new與釋放free記憶體)。

歸併排序不是原地排序,需要額外的儲存空間。並且在每次merge過程中,需要動態分配一塊記憶體以完成對兩個資料堆的排序合併。並且排序完畢之後,我們需要將儲存空間中的資料複製並覆蓋原序列。

最後一步操作是由歸併排序自身性質決定,無法優化,所以我們只能針對merge操作

經過分析很容易知道,對於長度為n的序列,要執行logn次的merge操作,這意味著需要進行logn次的記憶體分配和**。記憶體操作開銷較大。

如果能夠一次性分配長度為n的儲存空間,那麼就省掉了大量的分配操作,可以極大提高效率。

由於歸併的分治特性,我們需要在原來的函式基礎之上,包裝一層驅動函式(driver function)

// driver function

void _mergesort(int a, const

int count)

// devide the sequence recuresively

void _msort(int a, int tmpbuffer/*extra space*/, const

int left, const

int right)

return ;

}// merge two parts

void _merge(int a, int tmpbuffer, const

int lbegin, const

int rbegin, const

int rend)

while (l < rbegin)

while (r <= rend)

for (bufpos = lbegin; bufpos <= rend; ++bufpos)

return ;

}

為了檢驗效能提公升,筆者對100,1000,10000,100000的資料規模分別進行測試,每次測試排序執行100次,得到如下資料表和圖示。

ps:為了減少干擾,以上測試均在release下進行。

經過測試發現,改進後的演算法基本上比原始的要快30~50倍。至於**中1000測試那行,猜測和快取命中有關。多次測試後,基本也快出30-40倍。

以上改進可以得到乙個結論:對於需要頻繁分配記憶體的演算法而言,一次性分配或者採用lazy-deletion以提高復用的策略可以大幅提高演算法效率。

歸併排序 分治策略

歸併排序 merge sort 是利用歸併的思想實現的排序方法,該演算法採用經典的分治 divide and conquer 策略 分治法將問題分 divide 成一些小的問題然後遞迴求解,而治 conquer 的階段則將分的階段得到的各答案 修補 在一起,即分而治之 private var arr...

Memcache 記憶體分配策略

memcached預設採用了名為slab allocator的機制分配和管理記憶體。在該機制出現以前,記憶體的分配是通過對所有記錄簡單的進行malloc和free來進行了。但是這種方式會導致記憶體碎片化嚴重,加重作業系統記憶體管理器的負擔。slab allocator就是位了解決該問題而誕生的。sl...

(八)記憶體分配策略

在虛擬機器中,我們知道物件的記憶體是分配在堆中的。但是堆又可以劃分為更小的區域以便垃圾 那麼,物件到底是怎麼在分配在堆中的呢?大多數情況下,物件都在新生代的eden區中分配記憶體。而因為大部分的物件都是 朝生夕死 的,所以新生代又會頻繁進行垃圾 需要大量連續空間的物件,如 長字串 陣列等,會直接在老...