幾種設計良好結構以提高.NET應用性能的方法

来源:https://www.cnblogs.com/edison0621/archive/2019/06/24/11069653.html
-Advertisement-
Play Games

寫在前面 設計良好的系統,除了架構層面的優良設計外,剩下的大部分就在於如何設計良好的代碼,.NET提供了很多的類型,這些類型非常靈活,也非常好用,比如List,Dictionary、HashSet、StringBuilder、string等等。在大多數情況下,大家都是看著業務需要直接去用,似乎並沒有 ...


寫在前面

設計良好的系統,除了架構層面的優良設計外,剩下的大部分就在於如何設計良好的代碼,.NET提供了很多的類型,這些類型非常靈活,也非常好用,比如List,Dictionary、HashSet、StringBuilder、string等等。在大多數情況下,大家都是看著業務需要直接去用,似乎並沒有什麼問題。從我的實際經驗來看,出現問題的情況確實是少之又少。之前有朋友問我,我有沒有遇到過記憶體泄漏的情況,我說我寫的系統沒有,但是同事寫的我遇到過幾次。

為了記錄曾經發生的問題,也為了以後可以避免類似的問題,總結這篇文章,力圖從數據統計角度總結幾個有效提升.NET性能的方法。

本文基於.NET Core 3.0 Preview4,採用[Benchmark]進行測試,如果不瞭解Benchmark,建議瞭解完之後再看本文。

集合-隱藏的初始容量及自動擴容

在.NET里,List、Dictionary、HashSet這些集合類型都具有初始容量,當新增的數據大於初始容量時,會自動擴展,可能大家在使用的時候很少註意這個隱藏的細節(此處暫不考慮預設初始容量、載入因數、擴容增量)。

自動擴容給使用者的感知是無限容量,如果用的不是很好,可能會帶來一些新的問題。因為每當集合新增的數據大於當前已經申請的容量的時候,會再申請更大的記憶體容量,一般是當前容量的兩倍。這就意味著我們在集合操作過程中可能需要額外的記憶體開銷。

在本次測試中,我用到了四種場景,可能並不是很完全,但是很有說明性,每個方法都是迴圈了1000次,時間複雜度均為O(1000):

  • DynamicCapacity:不設置預設長度
  • LargeFixedCapacity:預設長度為2000
  • FixedCapacity:預設長度為1000
  • FixedAndDynamicCapacity:預設長度為100

下圖為List的測試結果,可以看到其綜合性能排名是FixedCapacity>LargeFixedCapacity>DynamicCapacity>FixedAndDynamicCapacity

list

下圖為Dictionary的測試結果,可以看到其綜合性能排名是FixedCapacity>LargeFixedCapacity>FixedAndDynamicCapacity>DynamicCapacity,在Dictionary場景中,FixedAndDynamicCapacity和DynamicCapacity的兩個方法性能相差並不大,可能是量還不夠大

dic

下圖為HashSet的測試結果,可以看到其綜合性能排名是FixedCapacity>LargeFixedCapacity>FixedAndDynamicCapacity>DynamicCapacity,在HashSet場景中,FixedAndDynamicCapacity和DynamicCapacity的兩個方法性能相差還是很大的

hashset

綜上所述:

一個恰當的容量初始值,可以有效提升集合操作的效率,如果不太好設置一個準確的數據,可以申請比實際稍大的空間,但是會浪費記憶體空間,併在實際上降低集合操作性能,編程的時候需要特別註意。

以下是List的測試源碼,另兩種類型的測試代碼與之基本一致:

   1:  public class ListTest
   2:  {
   3:      private int size = 1000;
   4:   
   5:      [Benchmark]
   6:      public void DynamicCapacity()
   7:      {
   8:          List<int> list = new List<int>();
   9:          for (int i = 0; i < size; i++)
  10:          {
  11:              list.Add(i);
  12:          }
  13:      }
  14:   
  15:      [Benchmark]
  16:      public void LargeFixedCapacity()
  17:      {
  18:          List<int> list = new List<int>(2000);
  19:          for (int i = 0; i < size; i++)
  20:          {
  21:              list.Add(i);
  22:          }
  23:      }
  24:   
  25:      [Benchmark]
  26:      public void FixedCapacity()
  27:      {
  28:          List<int> list = new List<int>(size);
  29:          for (int i = 0; i < size; i++)
  30:          {
  31:              list.Add(i);
  32:          }
  33:      }
  34:   
  35:      [Benchmark]
  36:      public void FixedAndDynamicCapacity()
  37:      {
  38:          List<int> list = new List<int>(100);
  39:          for (int i = 0; i < size; i++)
  40:          {
  41:              list.Add(i);
  42:          }
  43:      }
  44:  }

結構體與類

結構體是值類型,引用類型和值類型之間的區別是引用類型在堆上分配併進行垃圾回收,而值類型在堆棧中分配併在堆棧展開時被釋放,或內聯包含類型併在它們的包含類型被釋放時被釋放。 因此,值類型的分配和釋放通常比引用類型的分配和釋放開銷更低。

一般來說,框架中的大多數類型應該是類。 但是,在某些情況下,值類型的特征使得其更適合使用結構。

如果類型的實例比較小並且通常生存期較短或者通常嵌入在其他對象中,則定義結構而不是類。

該類型具有所有以下特征,可以定義一個結構:

  • 它邏輯上表示單個值,類似於基元類型(intdouble,等等)

  • 它的實例大小小於 16 位元組

  • 它是不可變的

  • 它不會頻繁裝箱

在所有其他情況下,應將類型定義為類。由於結構體在傳遞的時候,會被覆制,因此在某些場景下可能並不適合提升性能。

以上摘自MSDN,可點擊查看詳情

struct

可以看到Struct的平均分配時間只有Class的六分之一。

以下為該案例的測試源碼:

   1:  public struct UserStructTest
   2:  {
   3:      public int UserId { get;set; }
   4:   
   5:      public int Age { get; set; }
   6:  }
   7:   
   8:  public class UserClassTest
   9:  {
  10:      public int UserId { get; set; }
  11:   
  12:      public int Age { get; set; }
  13:  }
  14:   
  15:  public class StructTest
  16:  {
  17:      private int size = 1000;
  18:   
  19:      [Benchmark]
  20:      public void TestByStruct()
  21:      {
  22:          UserStructTest[] test = new UserStructTest[this.size];
  23:          for (int i = 0; i < size; i++)
  24:          {
  25:              test[i].UserId = 1;
  26:              test[i].Age = 22;
  27:          }
  28:      }
  29:   
  30:      [Benchmark]
  31:      public void TestByClass()
  32:      {
  33:          UserClassTest[] test = new UserClassTest[this.size];
  34:          for (int i = 0; i < size; i++)
  35:          {
  36:              test[i] = new UserClassTest
  37:              {
  38:                  UserId = 1,
  39:                  Age = 22
  40:              };
  41:          }
  42:      }
  43:  }

StringBuilder與string

字元串是不可變的,每次的賦值都會重新分配一個對象,當有大量字元串操作時,使用string非常容易出現記憶體溢出,比如導出Excel操作,所以大量字元串的操作一般推薦使用StringBuilder,以提高系統性能。

以下為一千次執行的測試結果,可以看到StringBuilder對象的記憶體分配效率十分的高,當然這是在大量字元串處理的情況,少部分的字元串操作依然可以使用string,其性能損耗可以忽略

image

這是執行五次的情況,可以發現雖然string的記憶體分配時間依然較長,但是穩定且錯誤率低

image

測試代碼如下:

   1:  public class StringBuilderTest
   2:  {
   3:      private int size = 5;
   4:   
   5:      [Benchmark]
   6:      public void TestByString()
   7:      {
   8:          string s = string.Empty;
   9:          for (int i = 0; i < size; i++)
  10:          {
  11:              s += "a";
  12:              s += "b";
  13:          }
  14:      }
  15:   
  16:      [Benchmark]
  17:      public void TestByStringBuilder()
  18:      {
  19:          StringBuilder sb = new StringBuilder();
  20:          for (int i = 0; i < size; i++)
  21:          {
  22:              sb.Append("a");
  23:              sb.Append("b");
  24:          }
  25:   
  26:          string s = sb.ToString();
  27:      }
  28:  }

析構函數

析構函數標識了一個類的生命周期已調用完畢時,會自動清理對象所占用的資源。析構方法不帶任何參數,它實際上是保證在程式中會調用垃圾回收方法 Finalize(),使用析構函數的對象不會在G0中處理,這就意味著該對象的回收可能會比較慢。通常情況下,不建議使用析構函數,更推薦使用IDispose,而且IDispose具有剛好的通用性,可以處理托管資源和非托管資源。

以下為本次測試的結果,可以看到記憶體平均分配效率的差距還是很大的

des

測試代碼如下:

   1:  public class DestructionTest
   2:  {
   3:      private int size = 5;
   4:   
   5:      [Benchmark]
   6:      public void NoDestruction()
   7:      {
   8:          for (int i = 0; i < this.size; i++)
   9:          {
  10:              UserTest userTest = new UserTest();
  11:          }
  12:      }
  13:   
  14:      [Benchmark]
  15:      public void Destruction()
  16:      {
  17:          for (int i = 0; i < this.size; i++)
  18:          {
  19:              UserDestructionTest userTest = new UserDestructionTest();
  20:          }
  21:      }
  22:  }
  23:   
  24:  public class UserTest: IDisposable
  25:  {
  26:      public int UserId { get; set; }
  27:   
  28:      public int Age { get; set; }
  29:   
  30:      public void Dispose()
  31:      {
  32:          Console.WriteLine("11");
  33:      }
  34:  }
  35:   
  36:  public class UserDestructionTest
  37:  {
  38:      ~UserDestructionTest()
  39:      {
  40:   
  41:      }
  42:   
  43:      public int UserId { get; set; }
  44:   
  45:      public int Age { get; set; }
  46:  }

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

-Advertisement-
Play Games
更多相關文章
  • django 路由系統 [TOC] 路由是什麼? Django 1.11版本 URLConf官方文檔 基本格式: 示例: 參考官方文檔 2.0版本中re_path和1.11版本的url是一樣的用法。 正則表達式 詳解 補充說明 分組 命名分組 (建議使用命名分組) 小結 指定預設值 include其 ...
  • 6.23 自我總結 1.描述符\_\_get\_\_,\_\_set\_\_,\_\_delete\_\_ 描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了___\_get\_\_(),\_\_set\_\_(),\_\_delete\_\_()中的一個,這也被稱為描述符協議 __ ...
  • JFxUtils "項目地址" 介紹 這是一個JFX的工具庫,Intent可以簡單地實現打開一個新視窗並傳遞數據,DialogBuilder可以簡單地生成對話框,MyUtils有些常用的功能 使用 與`JavaFxTemplate JavaFxTemplate`模板 JavaFxTemplate模版 ...
  • 6.23 自我總結 面向對象的高階 1.isinstance/type/issubclass 1.type 顯示對象的類,但是不會顯示他的父類 2.isinstance 會顯示的對象的類,也會去找對象的父類,填寫參數是對象,類isinstance(對象,類)如果對象屬於後面的類會報Ture,反之Fa ...
  • 摘要: 主頁面的搭建(導航條下麵的區域) 個人站點 側邊欄分類展示 側邊欄標簽展示 側邊欄日期歸檔 文章詳情頁 文章內容 文章點贊點踩 文章評論 側邊欄分類展示 側邊欄標簽展示 側邊欄日期歸檔 文章內容 文章點贊點踩 文章評論 一、主頁面home.html的搭建(進一步完善) home.html頁面 ...
  • 單線程執行 python的內置模塊提供了兩個內置模塊:thread和threading,thread是源生模塊,threading是擴展模塊,在thread的基礎上進行了封裝及改進。所以只需要使用threading這個模塊就能完成併發的測試 實例 創建並啟動一個單線程 執行結果 其實單線程的執行結果 ...
  • 對象操作流 可以用於讀寫任意類型的對象 ObjectOutputStream :對象輸出字元流 WriteObject ObjectInputStream :對象輸入字元流 ReadObject 註意: 使用對象輸出流寫出對象,只能使用對象輸入流來讀取對象 只能將支持java.io.Serializ ...
  • 1. 如何讓列表的內容更容易查找 假設有這麼一個列表(數據源在本地),由於內容太多,要查找到其中某個想要的數據會比較困難。要優化這個列表,無非就是排序、篩選和高亮。 改造過的結果如上。 2. 排序 在WPF中要實現數據排序的功能有很多種,例如用Linq,但這種場景的標準做法是使用 "Collecti ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...