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

1.背景

接上篇文章深刻淺出C#結構體——封裝以太網心跳包的結構爲例,使用結構體性能不佳,並且也說明了緣由。本篇文章詳細描述了以類來封裝網絡心跳包的優缺點,結果大大提高了解析性能。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);
        }

        /// <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;

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

7.測試性能對比

經過上圖能夠看到,上面的類解析的是微秒級別的,而文章深刻淺出C#結構體——封裝以太網心跳包的結構爲例解析的是幾十微秒級別的,差了差很少5到10倍的性能。ide

因而可知,在這種應用場景下,使用類來封裝網絡心跳包比結構體封裝更合理。函數

8.綜上,在C#裏,結構體主要做用有以下兩點:

  • 數據長度很短,構造16字節如下的新類型,並且結構體內的子類型必須是值類型,否則沒意義,其目的是爲了適應棧上的高效讀取;
  • 爲了兼容一些來自C跟C++的庫;
    避開以上兩點,我認爲在C#新開發的應用程序中,能夠徹底的用類來取代結構體(僅表明我的觀點)。

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

本文連接:http://www.javashuo.com/article/p-pqucqdpr-gu.html性能

相關文章
相關標籤/搜索