類與結構體性能對比測試——以封裝網路心跳包為例

来源:https://www.cnblogs.com/JerryMouseLi/archive/2020/04/01/12610332.html
-Advertisement-
Play Games

[toc] 1.背景 接上篇文章 "深入淺出C 結構體——封裝乙太網心跳包的結構為例" ,使用結構體性能不佳,而且也說明瞭原因。本篇文章詳細描述了以類來封裝網路心跳包的優缺點,結果大大提升瞭解析性能。 2.用類來封裝乙太網心跳包的優缺點 2.1.優點 + 可以在類里直接new byte[],即直接實 ...


目錄

1.背景

接上篇文章深入淺出C#結構體——封裝乙太網心跳包的結構為例,使用結構體性能不佳,而且也說明瞭原因。本篇文章詳細描述了以類來封裝網路心跳包的優缺點,結果大大提升瞭解析性能。

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);
        }

        /// <summary>
        /// 是否已經調用了 Dispose(bool disposing)方法。
        ///     應該定義成 private 的,這樣可以使基類和子類互不影響。
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// 所有回收工作都由該方法完成。
        ///     子類應重寫(override)該方法。
        /// </summary>
        /// <param name="disposing"></param>
        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;
            }
        }

        /// <summary>
        /// 該方法由程式調用,在調用該方法之後對象將被終結。
        ///     該方法定義在IDisposable介面中。
        /// </summary>
        public void Dispose()
        {
            //因為是由程式調用該方法的,因此參數為true。
            Dispose(true);
            //因為我們不希望垃圾回收器再次終結對象,因此需要從終結列表中去除該對象。
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 調用 Dispose() 方法,回收資源。
        /// </summary>
        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;

如下報錯,翻譯過來意思就是對象引用沒有對應的實例,也就是被我們給釋放掉了。

7.測試性能對比

通過上圖可以看到,上面的類解析的是微秒級別的,而文章深入淺出C#結構體——封裝乙太網心跳包的結構為例解析的是幾十微秒級別的,差了差不多5到10倍的性能。

由此可見,在這種應用場景下,使用類來封裝網路心跳包比結構體封裝更合理。

8.綜上,在C#里,結構體主要作用有如下兩點:

  • 數據長度很短,構造16位元組以下的新類型,而且結構體內的子類型必須是值類型,不然沒意義,其目的是為了適應棧上的高效讀取;
  • 為了相容一些來自C跟C++的庫;
    避開以上兩點,我認為在C#新開發的應用程式中,可以完全的用類來取代結構體(僅代表個人觀點)。

版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

本文鏈接:https://www.cnblogs.com/JerryMouseLi/p/12610332.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 有時可能會想要在byte數組中定位已知數組的位置(惡狠狠看了一眼某儀器文檔),以下代碼完成此功能: using System; using System.Collections.Generic; using System.Linq; namespace Hu { public class ByteM ...
  • 前言 C 的lambda和Linq可以說是一大亮點,C 的Lambda無處不在,Linq在數據查詢上也有著舉足輕重的地位。 那麼什麼是Linq呢,Linq是 (語言集成查詢)的縮寫,可以對本地對象 集合 或者遠程數據源進行結構化的查詢操作。 那什麼又是Lambda呢?嗯,簡單來講就是匿名函數,我們不 ...
  • 使用Xaml+C#,使WPF/UWP運行在Linux和Mac上 ...
  • 下麵的靜態代碼中: 現在想把箭頭所指的值,改為動態。 根據不同條件,它將有可能是1,或是3或是2或是5等。 ...
  • 四、C#表達式與運算符 4.1.表達式 操作數+運算符 4.2.數學運算符 var++ 先用後加 ++var 先加後用 4.3.賦值運算符 略 4.4.關係運算符 結果只會是bool類型 1)對象的不同 數值類型比較兩個數的大小 字元類比較Unicode編碼大小,'A'=65 'a'=97 '0'= ...
  • 三、C#數據類型 3.1.變數 聲明->賦值->使用 作用域:變數作用域為包含它的大括弧內 3.2.常量 1)const 數據類型 常量名稱 = 常量值 聲明常量時一定要賦值 2)@作用 輸出轉義字元 @"Hello World\n" 讓字元串換行 關鍵字用作標識符 @namespace @clas ...
  • 傳遞數據至部分視圖: 在ps.cshtml中get到上面高亮的參數: ...
  • VS2013如何轉成VS2010且不會出現此項目與Visual Studio的當前版本不相容的報錯 解決方法: 1.用記事本打開解決方案文件“解決方案名.sln”,然後修改最上面兩行為如下代碼:Microsoft Visual Studio Solution File, Format Version ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...