溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

.net編碼和設(shè)計(jì)原則的示例分析

發(fā)布時間:2021-09-17 10:56:09 來源:億速云 閱讀:129 作者:小新 欄目:編程語言

這篇文章主要為大家展示了“.net編碼和設(shè)計(jì)原則的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“.net編碼和設(shè)計(jì)原則的示例分析”這篇文章吧。

類 vs 結(jié)構(gòu)體

類的實(shí)例都是在堆上分配的,通過指針的引用進(jìn)行訪問。傳遞這些對象代價很低,因?yàn)樗皇且粋€指針(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)存對齊機(jī)制導(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ù)制。因?yàn)樗辉诙焉?,結(jié)構(gòu)體是不會導(dǎo)致垃圾回收的。

因此這里有一個折中。你可以找到各種關(guān)于結(jié)構(gòu)體尺寸大小的建議,但這里我不會告訴你一個確切的數(shù)字。在大多數(shù)情況下,你結(jié)構(gòu)體需要保持一個比較小的尺寸,特別是他們需要經(jīng)常被傳遞,你需要保證結(jié)構(gòu)體的大小不會造成太大的問題。唯一能確定的是,你需要根據(jù)自己的應(yīng)用場景進(jìn)行分析。

有些情況下,效率的差別還是蠻大的。當(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ù)組里的某一項(xiàng),需要先獲得該對象的指針引用,再去堆里訪問。迭代對象數(shù)組的時候,就會造成數(shù)據(jù)指針在堆里跳轉(zhuǎn),頻繁更新CPU的緩存,進(jìn)而浪費(fèi)了很多訪問CPU緩存數(shù)據(jù)機(jī)會。

在很多時候,通過改進(jìn)數(shù)據(jù)保存在內(nèi)存的位置,降低CPU訪問內(nèi)存的開銷是使用結(jié)構(gòu)體的一個主要原因,它可以顯著的提升性能。

因?yàn)榻Y(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;
}

問題是在最后一行,你試圖修改列表里Point元素的某個值,這個操作是不行的,因?yàn)閜oints[0]返回的是原始值的一個副本。正確的修改值的方式是

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

但是,你可以采取更嚴(yán)格的編碼策略:不要修改結(jié)構(gòu)體。一旦結(jié)構(gòu)體創(chuàng)建,永遠(yuǎn)不要改變他的值。這可以消除了上面的編譯問題,并簡化了結(jié)構(gòu)體的使用規(guī)則。
我之前提到,結(jié)構(gòu)體應(yīng)該保持小的體積,已避免花費(fèi)大量的時間來復(fù)制他們,但是偶爾也會使用一些大的結(jié)構(gòu)體。例如一個最終商業(yè)流程細(xì)節(jié)的對象,里面需要保存大量的時間戳:

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 ... 
}

為了簡化代碼,我們可以將時間的數(shù)據(jù)劃分到自己的子結(jié)構(gòu)里,這樣我們可以通過這樣的方式訪問Order對象:

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

我們可以把數(shù)據(jù)全部放到自己的類里:

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字節(jié)的開銷。如果你需要將OrderTimes對象作為一個整體傳入各種方法函數(shù)里,這也許是有一定道理的,但為什么不把Order對象傳入方法里呢?如果你同時有數(shù)千個Order對象,則可能會導(dǎo)致更多的垃圾回收,這是額外的對象增加的引用導(dǎo)致的。
相反,將OrderTime更改為結(jié)構(gòu)體,通過Order上的屬性(例如:Order.Times.ReceivedTime)訪問OrderTImes結(jié)構(gòu)體的各個屬性,不會導(dǎo)致結(jié)構(gòu)體的副本(.NET會對這個訪問做優(yōu)化)。這樣OrderTimes結(jié)構(gòu)體基本上成為Order類的內(nèi)存布局的一部分,幾乎和沒有子結(jié)構(gòu)體一樣了,你擁有了更加漂亮的代碼。
這種技術(shù)確實(shí)違反了不可變的結(jié)構(gòu)體原理,但這里的技巧就是將OrderTimes結(jié)構(gòu)的字段視為Order對象的字段。你不需要將OrderTimes結(jié)構(gòu)體作為一個實(shí)體進(jìn)行傳遞,它只是一個代碼組織方式。

以上是“.net編碼和設(shè)計(jì)原則的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI