李添銳,曹慶年,孟開(kāi)元
(西安石油大學(xué) 計(jì)算機(jī)學(xué)院,陜西 西安 710065)
排序問(wèn)題不僅是計(jì)算機(jī)科學(xué)中非常重要且應(yīng)用廣泛的問(wèn)題之一,而且在計(jì)算機(jī)圖形學(xué)、系統(tǒng)決策、搜索引擎等領(lǐng)域有著重要地位,并曾在2000 年被評(píng)為對(duì)工程和科學(xué)計(jì)算研究與實(shí)踐影響最大的十大問(wèn)題之一[1-4]。有資料顯示,系統(tǒng)在處理排序問(wèn)題上,中央處理器(Central Processing Unit,CPU)的時(shí)間占比很大,可達(dá)20%~60%[5]。在傳統(tǒng)的內(nèi)部排序算法中,由Hoare 提出的快速排序(Quick Sort)是一種效率相對(duì)較高的算法,但待排序列長(zhǎng)度較長(zhǎng)(大于106)時(shí),其時(shí)間成本依舊較高。因此十分有必要對(duì)快速排序算法進(jìn)行進(jìn)一步優(yōu)化。
線程(Thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算的最小單位。一個(gè)處理排序的進(jìn)程,若采用快速排序算法,其劃分的兩個(gè)子序列可分別由兩個(gè)并行執(zhí)行的線程完成。在劃分的子序列長(zhǎng)度相同的情況下,時(shí)間消耗僅為串行執(zhí)行的50%。將多線程技術(shù)引入快速排序算法,可以大大提高排序效率。
快速排序的主要步驟可以簡(jiǎn)化如下:
雖然快速排序的效率是內(nèi)部排序算法中最快的,但在算法第4 行中,若劃分的“標(biāo)桿”選取不當(dāng),在極端情況下,每次劃分的兩個(gè)子序列長(zhǎng)度都分別為0 和原長(zhǎng)度減1,此時(shí)不僅時(shí)間復(fù)雜度高至O(n2),且極易引起棧溢出(Stack Overflow)導(dǎo)致排序失敗。同時(shí)對(duì)于大量重復(fù)元素的待排序列,無(wú)論怎樣選取“標(biāo)桿”,均無(wú)法避免上述問(wèn)題。為了克服快速排序的缺點(diǎn),傳統(tǒng)的優(yōu)化方法如下。
可以先將序列中的隨機(jī)某個(gè)元素與首元素交換,再將首元素作為標(biāo)桿。此種方法被稱為“隨機(jī)標(biāo)桿”法,但會(huì)因?yàn)樯呻S機(jī)數(shù)而增加額外的開(kāi)銷?!叭龜?shù)取中”法可以避免此問(wèn)題,其基本思想是先將序列的中間元素、首元素和末元素進(jìn)行升序排序,再將首元素作為標(biāo)桿。但以上兩種方法都無(wú)法解決大量重復(fù)元素序列的問(wèn)題。
在每次序列劃分之后,從“標(biāo)桿”出發(fā),將兩個(gè)子序列中和“標(biāo)桿”相等的元素均交換到“標(biāo)桿”的左右兩邊,最終將原序列劃分成3 部分。通過(guò)采取適當(dāng)?shù)慕粨Q方法,排序整體的復(fù)雜度不會(huì)改變,同時(shí)減少了左右子序列的長(zhǎng)度。此種算法極大地改進(jìn)了大量重復(fù)元素的序列。
當(dāng)待排序列較短時(shí),采用分治遞歸操作所需的開(kāi)銷已經(jīng)大于簡(jiǎn)單排序的開(kāi)銷,故引入插入排序處理較短的子序列。此方法的難點(diǎn)在于最小分段閾值k的選取,k為經(jīng)驗(yàn)值,由系統(tǒng)的遞歸開(kāi)銷等因素決定。在Java 中,此k值取為32 效果最佳[6]。在C++的內(nèi)置sort()函數(shù)中,k值為16,一般情況下5~25 均可。
多核CPU 的出現(xiàn)使得多線程編程方法成為可能,同時(shí)也為許多傳統(tǒng)經(jīng)典算法進(jìn)一步優(yōu)化提供了新思路[7]。
快速排序中的遞歸操作中,可以創(chuàng)建子線程處理其中1 個(gè)子序列。在多核CPU 系統(tǒng)中,線程會(huì)分配到不同的核心上并行執(zhí)行,于是處理子序列的時(shí)間可大大縮短。引入多線程編程處理后的算法如下:
由于線程創(chuàng)建需要時(shí)間,在遞歸操作時(shí)可選用較短子序列交由子線程處理。
上述算法適用于無(wú)限創(chuàng)建線程的理想化模型,而實(shí)際情況中線程數(shù)受限于CPU 核心數(shù),故須改進(jìn)。引入多線程編程的深度d這一額外參數(shù),d=0 時(shí)為傳統(tǒng)串行方法;d>0 時(shí),構(gòu)建新線程執(zhí)行子序列,同時(shí)d自減1。d的值由系統(tǒng)CPU 核心數(shù)N決定,通常d取■log2N」。
當(dāng)待排序列為隨機(jī)序列時(shí),一次快排劃分后的兩個(gè)子序列的長(zhǎng)度不同。因此會(huì)出現(xiàn)先前創(chuàng)建的線程已經(jīng)運(yùn)行結(jié)束而釋放,后續(xù)的待排子序列無(wú)法創(chuàng)建新線程的現(xiàn)象。為此可以創(chuàng)建線程池,當(dāng)線程池飽和時(shí),采用傳統(tǒng)串行算法,這樣可以時(shí)刻充分利用CPU 的各個(gè)核心。
性能測(cè)試采用傳統(tǒng)串行快速排序和多線程優(yōu)化快速排序兩種方式分別對(duì)8 000 000 個(gè)隨機(jī)數(shù)據(jù)進(jìn)行排序,并對(duì)其時(shí)間消耗進(jìn)行對(duì)比,從而得出多線程優(yōu)化方法的改進(jìn)幅度。
在Intel(R)Core(TM)i7-6700 CPU@3.40 GHz 4核8 線程CPU 的PC 上使用C++多線程編程實(shí)現(xiàn)2.1中的算法并進(jìn)行測(cè)試。通過(guò)多次測(cè)試取平均值,測(cè)試結(jié)果如表1 所示。
表1 優(yōu)化結(jié)果比較
由于測(cè)試主機(jī)為4 核主機(jī),故算法執(zhí)行中最多有4個(gè)CPU 核心同時(shí)工作。從測(cè)試結(jié)果可以看出,4 核心多線程優(yōu)化快速排序算法的所需時(shí)間僅為傳統(tǒng)串行快速排序算法的1/4。
顯然,當(dāng)待排序列為升序序列,即經(jīng)過(guò)“三數(shù)取中”法后為最好情況時(shí),采用多線程編程的優(yōu)化幅度達(dá)到最大。
在最好的情況下,設(shè)長(zhǎng)度為n的序列的劃分操作時(shí)間消耗為P(n),不難得出:
其中,c為執(zhí)行1 條指令所需時(shí)間,設(shè)單位為us。
不妨設(shè)n=2^(k+1)-1,k=0,1,2,…如此可保證每次劃分都是最優(yōu)劃分。
傳統(tǒng)串行快速排序的時(shí)間消耗為:
推導(dǎo)后得:
若當(dāng)前CPU 的核心為2,多線程深度d=1,設(shè)系統(tǒng)創(chuàng)建1 個(gè)子線程的所需時(shí)間為td,此時(shí)多線程優(yōu)化快速排序的時(shí)間消耗F1(n)為:
推導(dǎo)后得:
相對(duì)于傳統(tǒng)單線程快速排序,時(shí)間節(jié)省率H1(n)為:
類似地,可以由數(shù)學(xué)歸納法得到d=k時(shí)的時(shí)間消耗Fk(n)和時(shí)間節(jié)省率Hk(n)
通過(guò)公式(9)可以看出,時(shí)間節(jié)省率Hk(n)是線程深度d和待排序列長(zhǎng)度n的函數(shù)。
在保證n?d的情況下,n和d越大,節(jié)省率越高。
為驗(yàn)證3.1 中的公式正確性,采用2.2 中的算法C++來(lái)實(shí)現(xiàn),待排序列長(zhǎng)度n取1 048 591,線程深度d分別取1,2,3,經(jīng)過(guò)測(cè)試,平均td=150 μs,c=1.5×10-3μs,待排序列取最優(yōu)序列。驗(yàn)證結(jié)果如表2 所示。
表2 理論驗(yàn)證結(jié)果
可以看出,實(shí)際運(yùn)行結(jié)果和理論分析極為接近,因此,3.1 的理論分析得到了驗(yàn)證。
快速排序是所有內(nèi)部排序算法中平均性能最優(yōu)的算法,但其本身具有許多不足之處。本文在C++平臺(tái)中實(shí)現(xiàn)了對(duì)快速排序遞歸操作的進(jìn)一步優(yōu)化,通過(guò)建立子線程并發(fā)處理兩個(gè)子序列,使得排序速度得到了大幅提高。
通過(guò)對(duì)多線程快速排序的理論分析,本文提出了此方法在優(yōu)化程度上的理論上限。在實(shí)際應(yīng)用中,由于每次劃分的子序列長(zhǎng)度不一,因此保證線程的利用率便成了多線程算法中的關(guān)鍵。
由于內(nèi)存和CPU 核心數(shù)量的限制,不宜做更大數(shù)據(jù)量和更多線程的測(cè)試。將來(lái)的實(shí)驗(yàn)中可以考慮其他環(huán)境,進(jìn)一步驗(yàn)證優(yōu)化理論,分析線程調(diào)度的開(kāi)銷等其他因素,以確定更精確的參數(shù),進(jìn)一步優(yōu)化排序性能。
和CPU 相比,GPU 的并行計(jì)算能力更加強(qiáng)大,使用CPU 優(yōu)化也是對(duì)快速排序的一種優(yōu)化方式,在未來(lái)的實(shí)驗(yàn)中也可嘗試使用GPU 優(yōu)化并和CPU 算法做對(duì)比[8]。