.NET性能優化-推薦使用Collections.Pooled

簡介

性能優化就是如何在保證處理相同數量的請求情況下占用更少的資源,而這個資源一般就是CPU或者內存,當然還有操作系統IO句柄、網絡流量、磁盤占用等等。但是絕大多數時候,我們就是在降低CPU和內存的占用率。
之前分享的內容都有一些局限性,很難直接改造,今天要和大家分享一個簡單的方法,只需要替換幾個集合類型,就可以達到提升性能和降低內存占用的效果。
今天要給大家分享一個類庫,這個類庫叫Collections.Pooled,從名字就可以看出來,它是通過池化內存來達到降低內存占用和GC的目的,后面我們會直接來看看它的性能到底怎么樣,另外也會帶大家看看源碼,為什么它會帶來這些性能提升。

Collections.Pooled

項目鏈接:https://github.com/jtmueller/Collections.Pooled
該庫基于System.Collections.Generic中的類,這些類已經被修改,以利用新的System.Span<T>System.Buffers.ArrayPool<T>類庫,達到減少內存分配,提高性能,并允許與現代API的更大的互操作性的目的。
Collections.Pooled支持.NETStandard2.0(.NET Framework 4.6.1+),以及針對.NET Core 2.1+的優化構建。一套廣泛的單元測試和基準已經從corefx移植過來。

測試總數:27501。通過:27501。失敗:0。跳過:0。
測試運行成功。
測試執行時間:9.9019秒

如何使用

通過Nuget就可以很簡單的安裝這個類庫,NuGet Version 。

Install-Package Collections.Pooled
dotnet add package Collections.Pooled
paket add Collections.Pooled

Collections.Pooled類庫中,它針對我們常使用的集合類型都實現了池化的版本,和.NET原生類型的對比如下所示。

.NET原生Collections.Pooled備注
List<T>PooledList<T>泛型集合類
Dictionary<TKey, TValue>PooledDictionary<TKey, TValue>泛型字典類
HashSet<T>PooledSet<T>泛型哈希集合類
Stack<T>Stack<T>泛型棧
Queue<T>PooledQueue<T>泛型隊列

在使用時,我們只需要將對應的.NET原生版本換成Collections.Pooled版本就可以了,如下方的代碼所示:

using Collections.Pooled;// 使用方式是一樣的
var list = new List<int>();
var pooledList = new PooledList<int>();var dictionary = new Dictionary<int,int>();
var pooledDictionary = new PooledDictionary<int,int>();// 包括PooledSet、PooledQueue、PooledStack的使用方法都是一樣的var pooledList1 = Enumerable.Range(0,100).ToPooledList();
var pooledDictionary1 = Enumerable.Range(0,100).ToPooledDictionary(i => i, i => i);

但是我們需要注意,Pooled類型實現了IDispose接口,它通過Dispose()方法將使用的內存歸還到池中,所以我們需要在使用完Pooled集合對象以后調用它的Dispose()方法。或者可以直接使用using var關鍵字。

using Collections.Pooled;// 使用using var 會在pooled對象使用完畢后自動釋放
using var pooledList = new PooledList<int>();
Console.WriteLine(pooledList.Count);// 使用using作用域 作用域結束以后就會釋放
using (var pooledDictionary = new PooledDictionary<int, int>())
{Console.WriteLine(pooledDictionary.Count);
}// 手動調用Dispose方法
var pooledStack = new PooledStack<int>();
Console.WriteLine(pooledStack.Count);
pooledList.Dispose();

注意:使用Collections.Pooled內的集合對象最好需要釋放掉它,不過不釋放也沒有關系,GC最終會回收它,只是它不能歸還到池中,達不到節省內存的效果了。
由于它會復用內存空間,在將內存空間返回到池中的時候,需要對集合內的元素做處理,它提供了一個叫ClearMode的枚舉供使用,定義如下:

namespace Collections.Pooled
{/// <summary>/// 這個枚舉允許控制在內部數組返回到ArrayPool時如何處理數據。/// 數組返回到ArrayPool時如何處理數據。在使用默認選項之外的其他選項之前,請注意了解 /// 在使用默認值Auto之外的任何其他選項之前,請仔細了解每個選項的作用。/// </summary>public enum ClearMode{/// <summary>/// <para><code>Auto</code>根據目標框架有不同的行為</para>/// <para>.NET Core 2.1: 引用類型和包含引用類型的值類型在內部數組返回池時被清除。不包含引用類型的值類型在返回池時不會被清除。</para>/// <para>.NET Standard 2.0: 在返回池之前清除所有用戶類型,以防它們包含引用類型。對于 .NET Standard,Auto 和 Always 具有相同的行為。</para>/// </summary>Auto = 0,/// <summary>/// The <para><code>Always</code> 設置的效果是在返回池之前總是清除用戶類型。/// </summary>Always = 1,/// <summary>/// <para><code>Never</code> 將導致池化集合在將它們返回池之前永遠不會清除用戶類型。</para>/// </summary>Never = 2}
}

默認情況下,使用默認值Auto即可,如果有特殊的性能要求,知曉風險后可以使用Never。
對于引用類型和包含引用類型的值類型,我們必須在將內存空間歸還到池的時候清空數組引用,如果不清除會導致GC無法釋放這部分內存空間(因為元素的引用一直被池持有),如果是純值類型,那么就可以不清空,在使用結構體替代類這篇文章中,我描述了引用類型和結構體(值類型)數組的存儲區別,純值類型沒有對象頭回收也無需GC介入。

性能對比

我沒有單獨做Benchmark,直接使用的開源項目的跑分結果,很多項目的內存占用都是0,那是因為使用的池化的內存,沒有多余的分配

PooledList<T>

在Benchmark中循環向集合添加2048個元素,.NET原生的List<T>需要110us(根據實際跑分結果,圖中的毫秒應該是筆誤)和263KB內存,而PooledList<T>只需要36us0KB內存。
6df5330bfb3a0477673afeeaeec7b115.png

PooledDictionary<TKey, TValue>

在Benchmark中循環向字典添加10_0000個元素,.NET原生的Dictionary<TKey, TValue>需要11ms13MB內存,而PooledDictionary<TKey, TValue>只需要7ms0MB內存。
508279df5a01d3af9262508e6126b4be.png

PooledSet<T>

在Benchmark中循環向哈希集合添加10_0000個元素,.NET原生的HashSet<T>需要5348ms2MB,而PooledSet<T>只需要4723ms0MB內存。
a8c4d9071c976d12e35401d0503ef51a.png

PooledStack<T>

在Benchmark中循環向棧添加10_0000個元素,.NET原生的PooledStack<T>需要1079ms2MB,而PooledStack<T>只需要633ms0MB內存。
3c9af40ebb51ef763f36343f4fe8118c.png

PooledQueue<T>

在Benchmark中循環向隊列添加10_0000個元素,.NET原生的PooledQueue<T>需要681ms1MB,而PooledQueue<T>只需要408ms0MB內存。
53af202ddcc5b9c6500d827da1432eba.png

未手動釋放場景

另外在上文中我們提到了Pooled的集合類型需要釋放,但是不釋放也沒有太大的關系,因為GC會去回收。

private static readonly string[] List = Enumerable  .Range(0, 10000).Select(c => c.ToString()).ToArray();  
// 使用默認的集合類型  
[Benchmark(Baseline = true)]  
public int UseList()  
{  var list = new List<string>(1024);  for (var index = 0; index < List.Length; index++)  {  var item = List[index];  list.Add(item);  }  return list.Count;  
}  
// 使用PooledList 并且及時釋放  
[Benchmark]  
public int UsePooled()  
{  using var list = new PooledList<string>(1024);  for (var index = 0; index < List.Length; index++)  {  var item = List[index];  list.Add(item);  }  return list.Count;  
}  
// 使用PooledList 不釋放  
[Benchmark]  
public int UsePooledWithOutUsing()  
{  var list = new PooledList<string>(1024);  for (var index = 0; index < List.Length; index++)  {  var item = List[index];  list.Add(item);  }  return list.Count;  
}

Benchmark結果如下:
91d6aa2e35fcc37c3affcf8022a38499.png
可以從上面的Benchmark結果可以得出結論。

  • 及時釋放Pooled類型集合幾乎不會觸發GC和分配內存,從上圖中它只分配了56Byte內存。

  • 就算不釋放Pooled類型集合,因為它從池中分配內存,在進行ReSize擴容操作時還是會復用內存,另外跳過了GC分配內存初始化步驟,速度也比較快。

  • 最慢的就是使用普通集合類型,每次ReSize擴容操作都需要申請新的內存空間,GC也要回收之前的內存空間。

原理解析

如果大家看過我之前的博文你應該為集合類型設置初始大小和淺析C# Dictionary實現原理就可以知道,.NET BCL開發人員為了高性能的隨機訪問,這些基本集合類型的底層數據結構都是數組,我們以List<T>為例。

  • 創建新的數組來存儲添加進來的元素。

  • 如果數組空間不夠,那么就觸發擴容操作,申請2倍的空間大小。
    構造函數代碼如下,可以看到是直接創建的泛型數組:

public List(int capacity)
{if (capacity < 0)ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);if (capacity == 0)_items = s_emptyArray;else_items = new T[capacity];
}

那么如果想要池化內存,只需要把類庫中使用new關鍵字申請的地方,改為使用池化的申請。這里和大家分享.NET BCL中的一個類型,叫ArrayPool,它提供了可重復使用的泛型實例的數組資源池,使用它可以降低對GC的壓力,在頻繁創建和銷毀數組的情況下提升性能。
而我們Pooled類型的底層就是使用ArrayPool來共享資源池,從它的構造函數中,我們可以看到它默認使用的是ArrayPool<T>.Shared來分配數組對象,當然你也可以創建自己的ArrayPool來讓它使用。

// 默認使用ArrayPool<T>.Shared池
public PooledList(int capacity, ClearMode clearMode, bool sizeToCapacity) : this(capacity, clearMode, ArrayPool<T>.Shared, sizeToCapacity) { }  // 分配數組使用 ArrayPool
public PooledList(int capacity, ClearMode clearMode, ArrayPool<T> customPool, bool sizeToCapacity)
{if (capacity < 0)ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);_pool = customPool ?? ArrayPool<T>.Shared;_clearOnFree = ShouldClear(clearMode);if (capacity == 0){_items = s_emptyArray;}else{_items = _pool.Rent(capacity);}if (sizeToCapacity){_size = capacity;if (clearMode != ClearMode.Never){Array.Clear(_items, 0, _size);}}}

另外在進行容量調整操作(擴容)時,會將舊的數組歸還回線程池,新的數組也在池中獲取。

public int Capacity
{get => _items.Length;set{if (value < _size){ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);}if (value != _items.Length){if (value > 0){// 從池中分配數組var newItems = _pool.Rent(value);if (_size > 0){Array.Copy(_items, newItems, _size);}// 舊數組歸還到池中ReturnArray();_items = newItems;}else{ReturnArray();_size = 0;}}}
}
private void ReturnArray()  
{  if (_items.Length == 0)  return;  try  {  // 歸還到池中_pool.Return(_items, clearArray: _clearOnFree);  }  catch (ArgumentException)  {  // ArrayPool可能會拋出異常,我們直接吞掉 }  _items = s_emptyArray;  
}

另外作者使用了Span優化了AddInsert等等API,讓它們有更好的隨機訪問性能;另外還加入了TryXXX系列API,可以更方便的方式的使用它。比如List<T>類相比PooledList<T>就有多達170個修改。
1a64abc7f8a0816319e4ea45c44f8ba4.png

總結

在我們線上實際的使用過程中,完全可以用Pooled提供的集合類型替代原生的集合類型,對降低內存占用率和P95延時有非常大的幫助。
另外就算忘記釋放了,那性能也不會比使用原生的集合類型差多少。當然最好的習慣就是及時的釋放它。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287072.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287072.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287072.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android之PC瀏覽器上傳表單格式大文件到手機客戶端read函數阻塞問題

1 、問題 PC瀏覽器上傳表單格式大文件到手機服務器端,然后read文件真實數據時候出現阻塞。 比如 User-Agent: PostmanRuntime/7.26.1Accept: */*Cache-Control: no-cachePostman-Token: c7e5e240-4398-4ac6-ba7f-98e99b5b4a01Host: 10.15.42.180:9999Accept-Encoding: gzip,…

避免活躍性危險(第十章)

2019獨角獸企業重金招聘Python工程師標準>>> 避免活躍性危險 在安全性與活躍性之間通常存在著某種制衡&#xff0c;我們使用加鎖機制來確保線程安全&#xff0c;但如果過度地使用加鎖&#xff0c;則可能導致“鎖順序死鎖”。同樣&#xff0c;我們使用線程池和信號量…

[poj2446]Chessboard

Description 給定一個mn的棋盤&#xff0c;上面有k個洞&#xff0c;求是否能在不重復覆蓋且不覆蓋到洞的情況下&#xff0c;用21的卡片完全覆蓋棋盤。 Input 第一行有三個整數n,m,k(0<m,n<32, 0<k<mn)&#xff0c;m表示行數&#xff0c;n表示列數。 接下來k行&…

Ubuntu下編譯內核

一、下載源代碼和編譯軟件的準備 下載內核源代碼&#xff1a;http://www.kernel.org/ 注意&#xff0c;點擊2.6.25內核的F版&#xff0c;即完整版。 如果你懶得去網站點聯接&#xff0c;運行下列命令&#xff1a; 代碼:$cd ~$ wget http://www.kernel.org/pub/linux/kernel/v2.…

(10)C#偷懶的開始永無止境的循環?

本系列文章將會以通俗易懂的對話方式進行教學&#xff0c;對話中將涵蓋了新手在學習中的一般問題。此系列將會持續更新&#xff0c;包括別的語言以及實戰都將使用對話的方式進行教學&#xff0c;基礎編程語言教學適用于零基礎小白&#xff0c;之后實戰課程也將會逐步更新。 若…

活照片 android,活照片app安卓

活照片app是當前國內一款最新的圖片處理應用軟件&#xff0c;能幫助大家快速進行最新的手機拍照、處理功能&#xff0c;當前活照片app已經推出了安卓、蘋果版本&#xff0c;可以幫助大家一鍵修圖&#xff0c;將你的圖片變得更加有趣。活照片app功能&#xff1a;它可以讓你的照片…

Jwt隱藏大坑,通過源碼揭秘

前言JWT是目前最為流行的接口認證方案之一&#xff0c;有關JWT協議的詳細內容&#xff0c;請參考&#xff1a;https://jwt.io/introduction今天分享一下在使用JWT在項目中遇到的一個問題&#xff0c;主要是一個協議的細節&#xff0c;非常容易被忽略&#xff0c;如果不是自己遇…

GPS實驗二:GPS接收機的使用

一、實習目的 1、了解GPS接收機的基本結構; 2、掌握GPS接收機的一般操作方法。 二、實習內容 1、了解GPS接收機的外觀及主要構成單元; 2、學習GPS接收機的安裝及靜態測量的操作方法; 3、了解GPS接收機工作時的基本狀態信息。 三、實習地點 選擇視野開闊的場所,視場…

Android之解決CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+RecyclerView里面再嵌套RecyclerView滑動顫抖問題

1 問題 主頁面用的是這種結構 CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+RecyclerView(里面再嵌套RecyclerView,然后這個RecyclerView再嵌套一個RecyclerView)上下滑動在AppBarLayout下面出現頁面上下顫抖問題 2 分析 我的主頁代碼如下 <?xml versio…

文件傳輸基礎——Java IO流

一、文件的編碼 1 package com.study.io;2 3 4 /**5 * 測試文件編碼6 */7 public class EncodeDemo {8 9 /** 10 * param args 11 * throws Exception 12 */ 13 public static void main(String[] args) throws Exception { 14 String s&quo…

keepalived實現nginx的高可用(雙主模型)

實驗環境&#xff1a;RS1&#xff1a;rip&#xff08;172.16.125.7&#xff09;&#xff0c;安裝httpd軟件包&#xff1b;RS2&#xff1a;rip&#xff08;172.16.125.8&#xff09;&#xff0c;安裝httpd軟件包&#xff1b;director1&#xff08;7-1.lcs.com&#xff09;&#…

【必懂C++】第一個程序當然是HelloWorld呀 01

作者簡介 作者名&#xff1a;1_bit 簡介&#xff1a;CSDN博客專家&#xff0c;2020年博客之星TOP5&#xff0c;藍橋簽約作者。15-16年曾在網上直播&#xff0c;帶領一批程序小白走上程序員之路。歡迎各位小白加我咨詢我相關信息&#xff0c;迷茫的你會找到答案。系列教程將會…

實現html5音樂的自動播放,html5中audio實現播放列表和自動播放

var count 43; //一共多少MP3文件var index 18.mp3; // 初始化播放那個文件window.onload function(){var audio new Audio();audio.preload true;audio.controls true;audio.loop false;audio.src index;document.body.appendChild(audio);audio.play();audio.addEven…

GPS實驗三:GPS接收機野外數據采集

一、實習目的 1、掌握GPS接收機的使用方法; 2、學會量取天線高 3、掌握選點和埋設標志的原則 二、實習內容 1、了解GPS接收機的外觀及主要構成單元; 2、學習GPS接收機的安裝及靜態測量的操作方法; 3、了解GPS接收機工作時的基本狀態信息。 三、實習地點 選擇視野開闊的…

打造操作系統根社區 統信Deepin屹立于浪潮之顛

如果把芯片比作信息系統的大腦的話&#xff0c;那么操作系統毫無疑問就是信息系統的靈魂。在過去幾十年里&#xff0c;我國信息產業飽受“缺芯少魂”的困擾&#xff0c;國內市場基本被微軟、谷歌、蘋果、IBM、紅帽等外商壟斷。誠然&#xff0c;一些國內廠商推出過基于Fedora、u…

Androd之在圖片右上角顯示紅色圓圈里面數字提醒

1 需求 在圖片右上角顯示紅色圓圈里面數字提醒 2 效果如圖 3 關鍵代碼 item_loca.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_wid…

Bean

Bean spring中把一切配置到IOC容器(其實就是那個xml文件)里面的對象都稱之為bean。 轉載于:https://www.cnblogs.com/Renyi-Fan/p/7780935.html

html原樣輸出html代碼

<xmp>********</xmp> 在網頁上顯示html代碼標記<xmp></xmp>有時我們會將html代碼顯示在網頁上,直接寫會有問題, 如果我們將要顯示的html代碼放在<xmp></xmp>中就可以實現轉載于:https://www.cnblogs.com/sign-ptk/p/5668442.html

ArcGIS實驗教程——實驗二十二:空間數據符號化

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據) 一、實驗描述 空間數據可視化是通過地圖語言實現的,地圖語言由符號、色彩和文字注記組成。 地圖符號由形狀不同、大小不一、色彩有別的圖形和文字組成,是地圖語言的圖解部分。 符號化是以圖形方…

【必懂C++】C++可真是個“固執”的小可愛 02

作者簡介 作者名&#xff1a;1_bit 簡介&#xff1a;CSDN博客專家&#xff0c;2020年博客之星TOP5&#xff0c;藍橋簽約作者。15-16年曾在網上直播&#xff0c;帶領一批程序小白走上程序員之路。歡迎各位小白加我咨詢我相關信息&#xff0c;迷茫的你會找到答案。系列教程將會…