本章介紹了本書其它部分未涉及到的一些編碼和設(shè)計原則。包含了一些.NET的應(yīng)用場景,有些不會造成太大危害,有些則會造成明顯的問題。剩下的則根據(jù)你的使用方法會產(chǎn)生不同的效果。如果要對本章節(jié)出現(xiàn)的原則做一個總結(jié),那就是:
過度的優(yōu)化會影響代碼的抽象
這意味著,當(dāng)你希望更高的優(yōu)化性能,你需要了解每個層次代碼的實現(xiàn)細(xì)節(jié)。本章會有很多相關(guān)介紹。
類 vs 結(jié)構(gòu)體
類的實例都是在堆上分配的,通過指針的引用進行訪問。傳遞這些對象代價很低,因為它只是一個指針(4或者8直接)的拷貝。然而,對象也有一些固定開銷:8或16字節(jié)(32或64位系統(tǒng))。這些開銷包括指向方法表的指針和用于其它目的同步字段。但是,如果通過調(diào)試工具查看一個空對象占用的內(nèi)存,這會發(fā)現(xiàn)大了13或者24字節(jié)(32位或64位系統(tǒng))。這是.NET的內(nèi)存對齊機制導(dǎo)致的。
而結(jié)構(gòu)體則沒上面的開銷,它的內(nèi)存使用量就是字段大小的綜合。如果結(jié)構(gòu)體是方法(函數(shù))里聲明的局部變量,則它在堆棧上分配控件。如果結(jié)構(gòu)體被聲明為類的一部分,這結(jié)構(gòu)體使用的內(nèi)存這是該類的內(nèi)存布局里的一部分(因此它會分配在堆上)。但你將結(jié)構(gòu)體傳遞給方法(函數(shù))時,他將對字節(jié)數(shù)據(jù)做復(fù)制。因為它不在堆上,結(jié)構(gòu)體是不會導(dǎo)致垃圾回收的。
因此這里有一個折中。你可以找到各種關(guān)于結(jié)構(gòu)體尺寸大小的建議,但這里我不會告訴你一個確切的數(shù)字。在大多數(shù)情況下,你結(jié)構(gòu)體需要保持一個比較小的尺寸,特別是他們需要經(jīng)常被傳遞,你需要保證結(jié)構(gòu)體的大小不會造成太大的問題。唯一能確定的是,你需要根據(jù)自己的應(yīng)用場景進行分析。
有些情況下,效率的差別還是蠻大的。當(dāng)一個對象開銷看起來不是很多,但是對比一個對象數(shù)組和結(jié)構(gòu)體數(shù)組就可以看出差別。在32位系統(tǒng)下,假設(shè)一個數(shù)據(jù)結(jié)構(gòu)包含16字節(jié)的數(shù)據(jù),數(shù)組長度是100w。
使用對象數(shù)組占用的空間
8字節(jié)數(shù)組開銷+
(4字節(jié)指針地址X1,000,000)+
((8字節(jié)頭部+16字節(jié)數(shù)據(jù))X1,000,000)
=28MB
使用結(jié)構(gòu)體數(shù)組占用的空間
8字節(jié)數(shù)組開銷+
(16字節(jié)數(shù)據(jù)X1,000,100)
=16MB
如果使用64位系統(tǒng),對象數(shù)組則使用40MB,而結(jié)構(gòu)體數(shù)組仍然是16MB。
可以看到,在一個結(jié)構(gòu)數(shù)組中,相同大小的數(shù)據(jù)占用的內(nèi)存小。隨著對象數(shù)組里對象的增加,還會增加GC的壓力。
除了空間,還有CPU效率問題。CPU有多級緩存。越靠近CPU的緩存越小,但訪問速度也會更快,對于順序保存的數(shù)據(jù)越容易優(yōu)化。
對于一個結(jié)構(gòu)體數(shù)組,他們在內(nèi)存里都是連續(xù)的值。訪問結(jié)構(gòu)體數(shù)組里數(shù)據(jù)很簡單,只要找到正確的位置就可以得到對應(yīng)的值。這就意味著在大數(shù)組數(shù)據(jù)做迭代訪問有巨大的差異。如果該值已經(jīng)在CPU的告訴緩存中,它的訪問速度是要比訪問RAM要快一個數(shù)量級。
如果要訪問對象數(shù)組里的某一項,需要先獲得該對象的指針引用,再去堆里訪問。迭代對象數(shù)組的時候,就會造成數(shù)據(jù)指針在堆里跳轉(zhuǎn),頻繁更新CPU的緩存,進而浪費了很多訪問CPU緩存數(shù)據(jù)機會。
在很多時候,通過改進數(shù)據(jù)保存在內(nèi)存的位置,降低CPU訪問內(nèi)存的開銷是使用結(jié)構(gòu)體的一個主要原因,它可以顯著的提升性能。
因為結(jié)構(gòu)體使用的時候總是被復(fù)制,所以編碼時要很小心,否則你會產(chǎn)生一些有趣的bug。例如下面的栗子,你是無法通過編譯的:
struct Point { public int x; public int y; }public static void Main(){ List<Point> points = new List<Point>(); points.Add(new Point() {x = 1, y = 2}); points[0].x = 3; }
問