類與結構體效能對比測試——以封裝網路心跳包為例
阿新 • • 發佈:2020-04-01
[toc]
#1.背景
接上篇文章[深入淺出C#結構體——封裝乙太網心跳包的結構為例](https://www.cnblogs.com/JerryMouseLi/p/12606920.html),使用結構體效能不佳,而且也說明了原因。本篇文章詳細描述了以類來封裝網路心跳包的優缺點,結果大大提升瞭解析效能。
#2.用類來封裝乙太網心跳包的優缺點
##2.1.優點
+ 可以在類裡直接new byte[],即直接例項位元組陣列,然後寫初始化方法或者建構函式中直接對傳進來的快取進行拷貝賦值;
+ 無需裝箱拆箱;
+ 類屬於引用型別,無需像結構體進行值拷貝,底層直接就是智慧指標;
+ 智慧指標指向同一片記憶體,省記憶體空間;
+ 可以在類裡寫很多方便的方法,這也就是面向物件,面向領域的基石,方便以後擴充套件;
##2.2.缺點
+ 存在堆裡,讀取效能會比棧稍差(現在PC端的計算速度很快,基本可忽略不計);
+ 雖然類也屬於GC的託管資源,但是GC什麼時候進行自動回收不可控制,需要實現IDisposable介面,用完該類,手動對該類進行釋放動作;
>使用類的實際效能怎樣,我們用測試資料說話,後面會放上與結構體測試的效能對比資料。
#3.網路心跳包封裝類
這裡全部都命名成了位元組陣列,包括 public byte[] type=new byte[1];因為如果是byte type型別,我不知道如何去釋放這一值型別,怕到時候引起記憶體洩露等問題。然後在建構函式裡面將快取buf拷貝到了類的各個屬性中,就是這麼簡單。
```
public class TcpHeartPacketClass: BaseDisposable
{
private bool _disposed; //表示是否已經被回收
public TcpHeartPacketClass(byte[] buf)
{
Buffer.BlockCopy(buf, 0, head, 0, 4);
type[0] = buf[4];
Buffer.BlockCopy(buf, 4, length, 0, 2);
Buffer.BlockCopy(buf, 6, Mac, 0, 6);
Buffer.BlockCopy(buf, 12, data, 0, 104);
Buffer.BlockCopy(buf, 116, tail, 0, 4);
}
protected override void Dispose(bool disposing)
{
if (!_disposed) //如果還沒有被回收
{
if (disposing) //如果需要回收一些託管資源
{
//TODO:回收託管資源,呼叫IDisposable的Dispose()方法就可以
}
//TODO:回收非託管資源,把之設定為null,等待CLR呼叫解構函式的時候回收
head = null;
type = null;
length = null;
Mac = null;
data = null;
tail = null;
_disposed = true;
}
base.Dispose(disposing);//再呼叫父類的垃圾回收邏輯
}
public byte[] head=new byte[4];
public byte[] type=new byte[1];
public byte[] length = new byte[2];
public byte[] Mac = new byte[6];
public byte[] data = new byte[104];//資料體
public byte[] tail = new byte[4];
}
```
#4.實現IDisposable介面
用完類之後,為了主動去釋放類,我封裝了一個釋放基類BaseDisposable。詳見程式碼註釋,有不明白的地方可以在評論區提問,我會詳細作答。
```
public class BaseDisposable : IDisposable
{
~BaseDisposable()
{
//垃圾回收器將呼叫該方法,因此引數需要為false。
Dispose(false);
}
///
/// 是否已經呼叫了 Dispose(bool disposing)方法。
/// 應該定義成 private 的,這樣可以使基類和子類互不影響。
///
private bool disposed = false;
///
/// 所有回收工作都由該方法完成。
/// 子類應重寫(override)該方法。
///
///
protected virtual void Dispose(bool disposing)
{
// 避免重複呼叫 Dispose 。
if (!disposed) return;
// 適應多執行緒環境,避免產生執行緒錯誤。
lock (this)
{
if (disposing)
{
// ------------------------------------------------
// 在此處寫釋放託管資源的程式碼
// (1) 有 Dispose() 方法的,呼叫其 Dispose() 方法。
// (2) 沒有 Dispose() 方法的,將其設為 null。
// 例如:
// xxDataTable.Dispose();
// xxDataAdapter.Dispose();
// xxString = null;
// ------------------------------------------------
}
// ------------------------------------------------
// 在此處寫釋放非託管資源
// 例如:
// 檔案控制代碼等
// ------------------------------------------------
disposed = true;
}
}
///
/// 該方法由程式呼叫,在呼叫該方法之後物件將被終結。
/// 該方法定義在IDisposable介面中。
///
public void Dispose()
{
//因為是由程式呼叫該方法的,因此引數為true。
Dispose(true);
//因為我們不希望垃圾回收器再次終結物件,因此需要從終結列表中去除該物件。
GC.SuppressFinalize(this);
}
///
/// 呼叫 Dispose() 方法,回收資源。
///
public void Close()
{
Dispose();
}
}
```
#5.應用層呼叫
```
DateTime packetClassStart = DateTime.Now;
TcpHeartPacketClass tcpHeartPacketClass = neTcpHeartPacketClass(ReceviveBuff);
DateTime packetClassEnd = DateTime.Now;
TimeSpan toClassTs = packetClassEnd.Subtra(packetClassStart);
try
{
tcpHeartPacketClass.head[0] = 0x11;
LoggerHelper.Info("類中的包頭:" + BitConverteToString(tcpHeartPacketClass.head));
Console.WriteLine("類中的包頭:{0}", BitConverteToString(tcpHeartPacketClass.head));
LoggerHelper.Info("類中的包型別:" tcpHeartPacketClass.type.ToString());
Console.WriteLine("類中的包型別:{0}"tcpHeartPacketClass.type.ToString());
LoggerHelper.Info("類中的包長度:" + BitConverteToString(tcpHeartPacketClass.length));
Console.WriteLine("類中的包長度:{0}", BitConverteToString(tcpHeartPacketClass.length));
LoggerHelper.Info("類中的MAC地址:" + BitConverteToString(tcpHeartPacketClass.Mac));
Console.WriteLine("類中的MAC地址:{0}", BitConverteToString(tcpHeartPacketClass.Mac));
LoggerHelper.Info("類中的註冊包內容:" + BitConverteToString(tcpHeartPacketClass.data));
Console.WriteLine("類中的註冊包內容:{0}"BitConverter.ToString(tcpHeartPacketClass.data));
LoggerHelper.Info("類中的包尾:" + BitConverteToString(tcpHeartPacketClass.tail));
Console.WriteLine("類中的包尾:{0}", BitConverteToString(tcpHeartPacketClass.tail));
Console.WriteLine("位元組陣列類中分割總共花費{0}ms\n"toClassTs.TotalMilliseconds);
}
finally
{
IDisposable disposable = tcpHeartPacketClass as IDisposable;
if (disposable != null)
disposable.Dispose();
}
```
#6.Dispose()方法生效的測試
在ty...finally塊執行完Dispose()方法之後,再去給類的某個屬性賦值,我們看是否報錯,如果報錯賦值給空物件則證明釋放成功。
```
finally
{
IDisposable disposable = tcpHeartPacketClass IDisposable;
if (disposable != null)
disposable.Dispose();
}
tcpHeartPacketClass.head[0] = 0x12;
```
如下報錯,翻譯過來意思就是物件引用沒有對應的例項,也就是被我們給釋放掉了。
![](https://img2020.cnblogs.com/blog/1606616/202004/1606616-20200401085512474-787217919.jpg)
#7.測試效能對比
![](https://img2020.cnblogs.com/blog/1606616/202004/1606616-20200401085429862-1960747615.jpg)
通過上圖可以看到,上面的類解析的是微秒級別的,而文章[深入淺出C#結構體——封裝乙太網心跳包的結構為例](https://www.cnblogs.com/JerryMouseLi/p/12606920.html)解析的是幾十微秒級別的,差了差不多5到10倍的效能。
由此可見,在這種應用場景下,使用類來封裝網路心跳包比結構體封裝更合理。
#8.綜上,在C#裡,結構體主要作用有如下兩點:
+ 資料長度很短,構造16位元組以下的新型別,而且結構體內的子型別必須是值型別,不然沒意義,其目的是為了適應棧上的高效讀取;
+ 為了相容一些來自C跟C++的庫;
避開以上兩點,我認為在C#新開發的應用程式中,可以完全的用類來取代結構體(僅代表個人觀點)。
版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。 本文連結:https://www.cnblogs.com/JerryMouseLi/p/12610
版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。 本文連結:https://www.cnblogs.com/JerryMouseLi/p/12610