本文將.netcore新出現的與Buffer操做相關的類型進行簡單分析與講解,因爲資料有限,一些看法爲我的看法,可能不是很準確。這些新類型將包括BinaryPrimitives、Span<>,Memory<>,ArrayPool<>,Memorypool<>數組
在網絡傳輸中,最小單位是byte,不少場景,咱們須要將int long short等類型與byte[]相互轉換。好比,將int轉換爲BigEndian的4個字節,在過去,咱們很容易就想到BitConverter,但BitConverter設計得不夠好友,BitConverter.GetBytes(int value)獲得的byte[]的字節順序永遠與主機的字節順序同樣,咱們不得再也不根據BitConverter的IsLittleEndian屬性判斷是否須要對獲得byte[]進行轉換字節順序,而BinaryPrimitives的Api設計爲嚴格區分Endian,每一個Api都指定了目標Endian。網絡
BitConverter異步
var intValue = 1; var bytes = BitConverter.GetBytes(intValue); if (BitConverter.IsLittleEndian == true) { Array.Reverse(bytes); }
BinaryPrimitives工具
var intValue = 1; var bytes = new byte[sizeof(int)]; BinaryPrimitives.WriteInt32BigEndian(bytes, intValue);
Span
讀寫代碼測試
public class DemoContext { private byte[] array = new byte[1024]; [Benchmark] public void ByteArray() { for (var i = 0; i < array.Length; i++) { array[i] = array[i]; } } [Benchmark] public void ByteSpan() { var span = array.AsSpan(); for (var i = 0; i < span.Length; i++) { span[i] = span[i]; } } [Benchmark] unsafe public void BytePointer() { fixed (byte* pointer = &array[0]) { for (var i = 0; i < array.Length; i++) { *(pointer + i) = *(pointer + i); } } } }
Benchmark報告this
| Method | Mean | Error | StdDev | |------------ |---------:|--------:|--------:| | ByteArray | 577.4 ns | 9.07 ns | 8.48 ns | | ByteSpan | 323.8 ns | 0.87 ns | 0.81 ns | | BytePointer | 499.4 ns | 4.09 ns | 3.82 ns |
若是嘗試將Span<>做爲全局變量,或在異步方法聲明爲變量,你會獲得編譯器的錯誤,緣由不在本文講解範圍內,而Memory<>類型能夠知足這些需求,Memory<>提供了用於數據讀寫的Span屬性,這個Span屬性是每將獲取時都有一些計算,因此咱們應該儘可能避免屢次獲取它的Span屬性。spa
合理的獲取Span.net
var span = memory.Span; for (var i = 0; i < span.Length; i++) { span[i] = span[i]; }
不合理的獲取Span設計
for (var i = 0; i < memory.Length; i++) { memory.Span[i] = memory.Span[i]; }
Benchmark報告
| Method | Mean | Error | StdDev | |------------ |-----------:|---------:|---------:| | ByteMemory1 | 325.8 ns | 1.03 ns | 0.97 ns | | ByteMemory2 | 3,344.9 ns | 11.91 ns | 11.14 ns |
ArrayPool<>用於解決頻繁申請內存和釋放內存致使GC壓力過大的場景,好比System.Text.Json在序列對象時爲utf8的byte[]時,事先是沒法計算最終byte[]的長度的,過程當中可能要不斷申請和調整緩衝區的大小。在沒有ArrayPool加持的狀況下,高頻次的序列化,則會生產高頻建立byte[]的過程,隨之GC壓力也會增大。ArrayPool的設計邏輯是,從pool申請一個指定最小長度的緩衝區,緩衝區在不須要的時候,將其返回到pool裏,待以重複利用。
var pool = ArrayPool<byte>.Shared; var buffer = pool.Rent(1024); // 開始利用buffer // ... // 使用結束 pool.Return(buffer);
Rent用於申請,其實是租賃,Return是歸還,返回到池中。咱們可使用IDisposable接口來包裝Return功能,使用上更方便一些:
/// <summary> /// 定義數組持有者的接口 /// </summary> /// <typeparam name="T"></typeparam> public interface IArrayOwner<T> : IDisposable { /// <summary> /// 獲取持有的數組 /// </summary> T[] Array { get; } /// <summary> /// 獲取數組的有效長度 /// </summary> int Count { get; } } /// <summary> /// 表示共享的數組池 /// </summary> public static class ArrayPool { /// <summary> /// 租賃數組 /// </summary> /// <typeparam name="T">元素類型</typeparam> /// <param name="minLength">最小長度</param> /// <returns></returns> public static IArrayOwner<T> Rent<T>(int minLength) { return new ArrayOwner<T>(minLength); } /// <summary> /// 表示數組持有者 /// </summary> /// <typeparam name="T"></typeparam> [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(ArrayOwnerDebugView<>))] private class ArrayOwner<T> :IDisposable, IArrayOwner<T> { /// <summary> /// 獲取持有的數組 /// </summary> public T[] Array { get; } /// <summary> /// 獲取數組的有效長度 /// </summary> public int Count { get; } /// <summary> /// 數組持有者 /// </summary> /// <param name="minLength"></param> public ArrayOwner(int minLength) { this.Array = ArrayPool<T>.Shared.Rent(minLength); this.Count = minLength; } /// <summary> /// 歸還數組 /// </summary> Public void Dispose() { ArrayPool<T>.Shared.Return(this.Array); } } /// <summary> /// 調試視圖 /// </summary> /// <typeparam name="T"></typeparam> private class ArrayOwnerDebugView<T> { [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get; } /// <summary> /// 調試視圖 /// </summary> /// <param name="owner"></param> public ArrayOwnerDebugView(IArrayOwner<T> owner) { this.Items = owner.Array.AsSpan(0, owner.Count).ToArray(); } } }
改造以後的使用
using var buffer = ArrayPool.Rent<byte>(1024); // 盡情的使用buffer吧,自動回收
Memorypool<>本質上仍是使用了ArrayPool<>,Memorypool只提供了Rent功能,返回一個IMomoryOwner<>,對其Dispose等同於Return過程,使用方式和咱們上面改造過的ArrayPool靜態類的使用方式是同樣的。
MemoryMarshal是一個工具類,相似於咱們指針操做時經常用到的Marshal類,它操做一些更底層的Span或Memory操做,好比提供將不一樣基元類型的Span相互轉換等。
獲取Span的指針
var span = new Span<byte>(new byte[] { 1, 2, 3, 4 }); ref var p0 = ref MemoryMarshal.GetReference(span); fixed (byte* pointer = &p0) { Debug.Assert(span[0] == *pointer); }
Span泛型參數類型轉換
Span<int> intSpan = new Span<int>(new int[] { 1024 }); Span<byte> byteSpan = MemoryMarshal.AsBytes(intSpan);
ReadonlyMemory<>轉換爲Memory
// 至關於給ReadonlyMemory移除只讀功能 Memory<T> MemoryMarshal.AsMemory<T>(ReadonlyMemory<T> readonly)