陣列排序方法的效能比較(5) 物件大小與排序效能

2021-09-08 14:26:52 字數 2826 閱讀 7004

在我公開測試結果之後,有朋友也進行了其他測試。在測試中我使用的是int陣列,經過分析之後我們了解到array.sort對於int陣列有特殊的優化。於是,某些朋友使用了一些引用型別的陣列進行排序,得到array.sort方法的效能落後於linq排序——雖然由於測試方式的問題,這個結果和結論都不太妥當。不過在討論的過程中,我們都意識到了乙個問題:在其他條件不變的情況下,引用型別的字段越多,array.sort方法所需時間就越久。這次我們就來討論一下這個問題。

public abstract class 

typebase

class

program

; var domain = thread.getdomain();

var asmbuilder = domain.definedynamicassembly(

assemblyname, assemblybuilderaccess.run);

modulebuilder = asmbuilder.definedynamicmodule("somemodule");

}static

type createtype(int numberoffield)

return typebuilder.createtype();}}

方便起見,我讓每種動態型別都繼承統一的typebase類,這樣我們排序的目標便可以定為typebase陣列,而作為比較器的typebasecomparer也可以直接訪問id欄位。然後便是測試用的方法:

static void main(string args)

codetimer.time(

string.format("type with fields (array.sort)", num),

10, () => sort(clonearray(arraytosort)));

codetimer.time(

string.format("type with fields (arraysorter)", num),

10, () => arraysorter(clonearray(arraytosort)));

}console.readline();

}static void sort(typebase array)

static void arraysorter(typebase array)

在比較時,我們將測試從1個字段的型別開始,每次將字段數量翻倍,直至512個字段——雖然奪得有些誇張,但對於試驗來說,我還是希望差距能夠明顯一些。既然說array.sort的效能受物件體積影響比較明顯,而linq排序相對穩定,那麼我們就來比較array.sort以及與linq排序實現類似的arraysorter的執行時間吧。請注意,除了兩種排序方式之外,其他條件都完全相同:相同的數量,相同的型別,相同的初始順序,以及相同的「比較依據」。

測試結果如下:

繪製成圖表:

這是為什麼呢?在閱讀以下內容時,您不如先自己思考一下?

其實這還是乙個和「區域性性」有關的問題。區域性性是影響效能非常主要的因素之一,我之前也不止一次談過這個問題。那麼在這裡,區域性性又是如何影響排序效率的呢?其實關鍵還是在於「比較器」上:

public class 

typebasecomparer : icomparer

}

typebasecomparer的實現非常簡單,只是把兩個typebase物件的id值相減而已。顯然,系統在執行這段程式的時候,會根據x或y的位址及id欄位的偏移量計算出乙個位址,然後再去讀取資料。不過我們知道,cpu在讀取某個位址的的資料時,還會同時載入「一整條」的資料並快取起來,這樣再次讀取附近的資料時會顯得更快一些。那麼試想一下,對於cpu來說,快取及每個條目的大小是不變的,因此隨著物件的體積增加,快取中可以同時存在的物件數量便少了,這樣雖然讀取的次數不變,但是快取的命中率就會隨之下降。於是,物件體積增大,排序所消耗的時間也隨之增加。

但是對於arraysorter來說就完全不同了。根據arraysorter的實現機制,它會首先根據keyselector得到排序字段——它在上例中就是個int值,indexcomparer然後在排序的時候將這個int陣列儲存起來,並且在排序時使用。因此,無論物件的體積是多少,在排序時arraysorter永遠只是在訪問乙個十分緊湊的int陣列而已。排序結束後,arraysorter也只是在操作乙個個物件引用,它同樣與物件的體積無關。由於排序其他條件不變,因此物件體積增大(造成區域性性不佳)最終也只是導致keyselector工作的時候速度變慢,而其他部分統統不受影響。自然從某一時刻開始arraysorter的效能會超過array.sort,並且兩者的差距會越來越大。

如果您對現在談論的內容不很理解的話,可以了解一下與「區域性性」有關的內容,並了解一下linq排序及arraysorter的實現方式。

可見,array.sort與arraysorter的效能高低並非是一句兩句話可以說清楚的,在真實條件下選用哪種做法更有優勢也不是一件容易確定的事情。不過,我們真需要把效能追求到這個地步嗎?事實上,我相信在大部分實際的開發過程中,這點效能差距可以說是微乎其微。對於系統自帶的array.sort方法以及linq排序,其實它們並沒有明顯的替代關係。其實在某個特定的時候,您會發現其實兩者間也只有一種符合條件——別多想,就用吧。

當然,一些明顯的錯誤是需要避免的。例如,您在比較器中反覆訪問資料庫的話,這就是您自己的問題了。

此外,我們這篇文章著重於測試和分析,但是如果可以直觀地觀察到「區域性性」相關的資料豈不是更能說明問題嗎?那麼我們有什麼辦法通過實驗來證明這一點呢?這問題其實也不大。例如,在使用visual studio 2008的profiler時,我們可以讓它同時監視l2 cache的情況。這個實驗的設計和執行就交由您親自來吧。

經過了這幾篇文章,您對於.net框架中的排序類庫,是否還有什麼疑惑呢?

各種排序方法的效能比較

測試環境說明 win xp下,vs2008,主頻 core2 雙核2.53ghz 下面是測試的 using system using system.collections.generic using system.linq using system.text using system.collect...

JS 陣列求和方法與效能比較

function sum arr function sum arr return res function sum arr function sum arr function sum arr 執行效率 引數生成const oriarr array.from new array 100000 keys...

分頁實現方法的效能比較

我們先給出幾種主要的分頁方法和核心語句,然後直接給出結論,有興趣的讀者可以看看後面的資料 幾種常用儲存過程分頁方法 topn方法 select top pagesize from tablename where id not in select top pageindex 1 pagesize id...