總結一些編碼和設計原則執行個體

來源:互聯網
上載者:User
本章介紹了本書其它部分未涉及到的一些編碼和設計原則。包含了一些.NET的應用情境,有些不會造成太大危害,有些則會造成明顯的問題。剩下的則根據你的使用方法會產生不同的效果。如果要對本章節出現的原則做一個總結,那就是:

過度的最佳化會影響代碼的抽象
這意味著,當你希望更高的最佳化效能,你需要瞭解每個層次代碼的實現細節。本章會有很多相關介紹。

類 vs 結構體

類的執行個體都是在堆上分配的,通過指標的引用進行訪問。傳遞這些對象代價很低,因為它只是一個指標(4或者8直接)的拷貝。然而,對象也有一些固定開銷:8或16位元組(32或64位系統)。這些開銷包括指向方法表的指標和用於其它目的同步欄位。但是,如果通過調試工具查看一個Null 物件佔用的記憶體,這會發現大了13或者24位元組(32位或64位系統)。這是.NET的記憶體對齊機制導致的。

而結構體則沒上面的開銷,它的記憶體使用量量就是欄位大小的綜合。如果結構體是方法(函數)裡聲明的局部變數,則它在堆棧上分配控制項。如果結構體被聲明為類的一部分,這結構體使用的記憶體這是該類的記憶體布局裡的一部分(因此它會分配在堆上)。但你將結構體傳遞給方法(函數)時,他將對位元組資料做複製。因為它不在堆上,結構體是不會導致記憶體回收的。

因此這裡有一個折中。你可以找到各種關於結構體尺寸大小的建議,但這裡我不會告訴你一個確切的數字。在大多數情況下,你結構體需要保持一個比較小的尺寸,特別是他們需要經常被傳遞,你需要保證結構體的大小不會造成太大的問題。唯一能確定的是,你需要根據自己的應用情境進行分析。

有些情況下,效率的差別還是蠻大的。當一個對象開銷看起來不是很多,但是對比一個對象數組和結構體數組就可以看出差別。在32位系統下,假設一個資料結構包含16位元組的資料,數組長度是100w。
使用對象數組佔用的空間
8位元組數組開銷+
(4位元組指標地址X1,000,000)+
((8位元組頭部+16位元組資料)X1,000,000)
=28MB

使用結構體數組佔用的空間
8位元組數組開銷+
(16位元組資料X1,000,100)
=16MB

如果使用64位系統,對象數組則使用40MB,而結構體數組仍然是16MB。
可以看到,在一個結構數組中,相同大小的資料佔用的記憶體小。隨著對象數組裡對象的增加,還會增加GC的壓力。
除了空間,還有CPU效率問題。CPU有多級緩衝。越靠近CPU的緩衝越小,但訪問速度也會更快,對於順序儲存的資料越容易最佳化。
對於一個結構體數組,他們在記憶體裡都是連續的值。訪問結構體數組裡資料很簡單,只要找到正確的位置就可以得到對應的值。這就意味著在大數組資料做迭代訪問有巨大的差異。如果該值已經在CPU的告訴緩衝中,它的訪問速度是要比訪問RAM要快一個數量級。

如果要訪問對象數組裡的某一項,需要先獲得該對象的指標引用,再去堆裡訪問。迭代對象數組的時候,就會造成資料指標在堆裡跳轉,頻繁更新CPU的緩衝,進而浪費了很多訪問CPU快取資料機會。

在很多時候,通過改進資料儲存在記憶體的位置,降低CPU訪問記憶體的開銷是使用結構體的一個主要原因,它可以顯著的提升效能。

因為結構體使用的時候總是被複製,所以編碼時要很小心,否則你會產生一些有趣的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;}

問題是在最後一行,你試圖修改列表裡Point元素的某個值,這個操作是不行的,因為points[0]返回的是原始值的一個副本。正確的修改值的方式是

Point p = points[0];p.x = 3;points[0] = p;

但是,你可以採取更嚴格的編碼策略:不要修改結構體。一旦結構體建立,永遠不要改變他的值。這可以消除了上面的編譯問題,並簡化了結構體的使用規則。
我之前提到,結構體應該保持小的體積,已避免花費大量的時間來複製他們,但是偶爾也會使用一些大的結構體。例如一個最終商業流程細節的對象,裡面需要儲存大量的時間戳記:

class Order{    public DateTime ReceivedTime { get; set; }    public DateTime AcknowledgeTime { get; set; }    public DateTime ProcessBeginTime { get; set; }    public DateTime WarehouseReceiveTime { get; set; }    public DateTime WarehouseRunnerReceiveTime { get; set; }    public DateTime WarehouseRunnerCompletionTime { get; set; }    public DateTime PackingBeginTime { get; set; }    public DateTime PackingEndTime { get; set; }    public DateTime LabelPrintTime { get; set; }    public DateTime CarrierNotifyTime { get; set; }    public DateTime ProcessEndTime { get; set; }    public DateTime EmailSentToCustomerTime { get; set; }    public DateTime CarrerPickupTime { get; set; }    // lots of other data ... }

為了簡化代碼,我們可以將時間的資料劃分到自己的子結構裡,這樣我們可以通過這樣的方式訪問Order對象:

Order order = new Order(); Order.Times.ReceivedTime = DateTime.UtcNow;

我們可以把資料全部放到自己的類裡:

class OrderTimes    {        public DateTime ReceivedTime { get; set; }        public DateTime AcknowledgeTime { get; set; }        public DateTime ProcessBeginTime { get; set; }        public DateTime WarehouseReceiveTime { get; set; }        public DateTime WarehouseRunnerReceiveTime { get; set; }        public DateTime WarehouseRunnerCompletionTime { get; set; }        public DateTime PackingBeginTime { get; set; }        public DateTime PackingEndTime { get; set; }        public DateTime LabelPrintTime { get; set; }        public DateTime CarrierNotifyTime { get; set; }        public DateTime ProcessEndTime { get; set; }        public DateTime EmailSentToCustomerTime { get; set; }        public DateTime CarrerPickupTime { get; set; }    }    class Order    {        public OrderTimes Times;    }

但是,這樣會為每個Order對象引入額外的12或者24位元組的開銷。如果你需要將OrderTimes對象作為一個整體傳入各種方法函數裡,這也許是有一定道理的,但為什麼不把Order對象傳入方法裡呢?如果你同時有數千個Order對象,則可能會導致更多的記憶體回收,這是額外的對象增加的引用導致的。
相反,將OrderTime更改為結構體,通過Order上的屬性(例如:Order.Times.ReceivedTime)訪問OrderTImes結構體的各個屬性,不會導致結構體的副本(.NET會對這個訪問做最佳化)。這樣OrderTimes結構體基本上成為Order類的記憶體布局的一部分,幾乎和沒有子結構體一樣了,你擁有了更加漂亮的代碼。
這種技術確實違反了不可變的結構體原理,但這裡的技巧就是將OrderTimes結構的欄位視為Order對象的欄位。你不需要將OrderTimes結構體作為一個實體進行傳遞,它只是一個程式碼群組織方式。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.