首頁  >  文章  >  後端開發  >  總結一些編碼和設計原則實例

總結一些編碼和設計原則實例

零下一度
零下一度原創
2017-06-24 10:36:271594瀏覽

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

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

類別 vs 結構體

類別的實例都是在堆上分配的,透過指標的引用進行存取。傳遞這些物件代價很低,因為它只是一個指標(4或8直接)的拷貝。然而,物件也有一些固定開銷:8或16位元組(32或64位元系統)。這些開銷包括指向方法表的指標和用於其它目的同步欄位。但是,如果透過調試工具查看一個空物件佔用的內存,這會發現大了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结构体作为一个实体进行传递,它只是一个代码组织方式。

以上是總結一些編碼和設計原則實例的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn