.net開發者應掌握的利器CommunityToolkit.HighPerformance——MemoryOwner與SpanOwner

2023-06-01 06:00:45

MemoryOwner和SpanOwner都可以理解為是對ArrayPool<>的一個包裝,無非一個是在堆疊上,一個是在託管堆上。既然做了包裝,那肯定隨之而來就是改進和優化。

MemoryOwner<>

MemoryOwner<>解決的問題

1.通過ArrayPool的Api MemoryPool.Shared.Rent(size) 獲得的IMemoryPool範例的緩衝區會大於我們指定的大小,一般是2的n次方,所以需要我們去切片去獲取真正有意義的段資料。但是MemoryOwner<>會儲存我們請求的大小,並且在其屬性中,如span,自動為我們切片成我們請求的大小,從而免除了切片的操作。

2.IMemoryPool只有Memory屬性,我們需要通過Memory再去獲取span,但是MemoryOwner<>本身就有span屬性供我們呼叫

3.ArrayPool返回我們租用的緩衝空間的時候預設不會清空,除非設定clearArray,MemoryOwner<>在Allocate的時候就可以設定AllocationMode從而決定在返還緩衝區的時候是否清空其中資料。
但是清空資料將會帶來小小的效能消耗,就是我們需要給每一位填上預設值,即填充0

4.MemoryOwner<>最大的優點,就是會重複使用緩衝池中的某個相同的陣列,從而最大程度的避免分配。

如下是我們使用原始陣列來儲存從檔案讀取到的資料:

using Stream stream = File.OpenRead(path);
byte[] buffer = new byte[(int)stream.Length];
stream.Read(buffer, 0, buffer.Length);
return buffer;

如果我們讀取的是一個大檔案,則會在記憶體中分配一個大記憶體空間,這會在使用完之後給GC很大的壓力。

我們使用ArrayPool對程式碼做一個優化,目的在於從緩衝池中租用一段空間,以避免空間的分配。

using Stream stream = File.OpenRead(path);
byte[] buffer = ArrayPool<byte>.Shared.Rent((int)stream.Length);
stream.Read(buffer, 0, (int)stream.Length);
//切片陣列
return buffer[0..((int)stream.Length - 1)];

上述程式碼有一個很明顯的問題,就是最終我們對陣列做了切片,所以還是將舊的緩衝區的資料拷貝到了一個新的陣列,並且還存在了分配空間的行為。問題的根源就是ArrayPool租用到的大小實際會大於我們的實際請求。
並且我們返回了陣列,那麼我們還需要再去跟蹤這個陣列的使用的生命週期,並且需要再合適的時機去呼叫ArrayPool<>.Shared.Return(buffer)返還到緩衝池中。

為了解決上述問題,我們再使用MemoryOwner<>去重構程式碼

using Stream stream = File.OpenRead(path);
MemoryOwner<byte> buffer = MemoryOwner<byte>.Allocate((int)stream.Length);
stream.Read(buffer.Span);
return buffer;

MemoryOwner<>.Allocate(size)返回的IMemoryOwner<> 範例將負責釋放基礎緩衝區
並且MemoryOwner中的所有屬性遵循我們請求的實際大小,從而無需再做切片處理,比如Span屬性。

SpanOwner<>

SpanOwner<>是從共用記憶體池租用再堆疊中的緩衝區的型別,功能和API與MemoryOwner<>類似。
和MemoryOwner<>的區別就是它在堆疊上以及它沒有實現IMemoryOwner<>這個介面所以沒有Memory<>屬性。
程式碼範例:

SpanOwner<int> buffer = SpanOwner<int>.Allocate(length);
Span<int> span = buffer.Span;

總結

1.SpanOwner和MemoryOwner常被用作緩衝區,儲存臨時資料
2.可以使用using對SpanOwner和MemoryOwner進行生命週期的控制,對於MemoryOwner來說,如果不方便控制,GC最後也會將其返還到緩衝池中。
3.SpanOwner和MemoryOwner可以理解為是對ArrayPool的包裝。

本文參考檔案:https://learn.microsoft.com/zh-cn/dotnet/communitytoolkit/high-performance/memoryowner

如有問題,多謝指教!