BigMemory<T>把底層托管數組保存在 _storage裏,构建BigSpan 和 BigMemory
隻有持有存儲的托管類型還不夠
。因此代碼隻需要拿到第一個邏輯 T的上数组引用
,
public BigArray(nint length){ if ((nuint)length > (nuint)MaxLength) { ThrowHelpers.ThrowOutOfRange(nameof(length)); } if (length <= Array.MaxLength) { _storage = new ElementChunk1<T>[length]; } else { _storage = CreateBigArraySlow(length); } _length = length;}然後是构建索引器實現。隻是托管在同一段數組數據區裏繼續往前走。可以存下 40 億個字節
。上数组也就是构建 T[]
。反射以及大量現有代碼。托管最大長度會隨塊大小增長。上数组並且在需要和現有 API 互操作時,构建BigSpan<T>和 BigMemory<T>,托管byte[1024]存 1024 字節 ,上数组
麻煩的构建地方在於,隻要覆蓋 65535 / size可能產生的托管那些值就夠了
。BigArray<T>不需要像交錯數組包裝器那樣在每次訪問時都做除法和取餘;它隻是把一個托管數組對象視作一段更大的邏輯序列。
[InlineArray(2)]struct ElementChunk2<T>{ private T _first;}[InlineArray(3)]struct ElementChunk3<T>{ private T _first;}ElementChunk2<ElementChunk3<T>>表示 2 個包含 3 個值的塊,為了覆蓋 1 到 65,535 之間需要的塊長度,尤其是在大分配的情況下
。那麽四倍寬度的塊就能表示接近 80 億個邏輯元素。塊長度是:
65535 / Unsafe.SizeOf<T>()所以 byte可以使用 65,535 的塊長度。
基本思路
在 .NET 中,
BigArray
有了塊機製之後,每個分支都返回一個靜態 lambda
,split
、大小為 32 字節的類型可以使用 2,047。隻有和當前 Unsafe.SizeOf<T>()匹配的塊形狀會真正實例化,這裏當然說的是理論上限,大約是 Array.MaxLength * 65535;對 64 位運行時上的 long或對象引用來說,數組數據區裏連續排列著塊結構體,所以合法的塊長度是 8,191:
65535 / 8 = 8191這意味著 ElementChunk8191<object>是合法的。不需要清零的性能敏感場景,
這也意味著實現不需要為每一個整數都準備一個塊類型 。避免每一次邏輯訪問都再走一次普通數組邊界檢查。
這就是 BigArray<T>的核心思路
。
數據引用是通過把數組數據開頭重新解釋為 T得到的 :
private static ref T GetDataReference(Array storage){ return ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(storage));}這就是為什麽連續存儲這個特性很重要。它可以讓一個 struct 表示固定數量的重複字段
,我們還會用 Span<T>、
手動管理內存很容易出錯 ,trim 、
分配器來自一個針對塊長度的 switch。也就是 6 個邏輯 T。機器仍然需要真的有足夠的內存。和那些期待連續內存區域的 API 配合起來也很別扭。一個引用是 8 字節 ,對於 object,
BigSpan<T>是一個麵向超大連續區域的棧上視圖:
public readonly ref struct BigSpan<T>{ internal readonly ref T _first; internal readonly nint _length;}它的基本形狀和 Span<T>一樣:一個起始引用加一個長度
。我們就可以用接近普通數組的方式處理超大的連續托管內存。或者為每一個長度準備一個 struct 要容易維護得多
。JIT 和類型加載器在導入或編譯方法時,公共 API 仍然是安全的;對實現來說
,而且對任意 T來說也不一定合法。這時最後一個塊隻使用 1 個字節,反射和基礎類庫等很多地方。其他長度都可以由這些基礎長度相乘得到。比如邏輯長度是 10,000,但能不能分配到需要的內存更重要。會在到達這條路徑之前失敗
。然後實現使用引用偏移,和 BigArray<T>暴露出來的邏輯長度不同。
類型加載
現在假設 T是 64 位運行時上的 object。排序、也就是 65,535,再通過嵌套組合出其他長度。隨機訪問模式也可能比小數組慢。
struct TwoBytes{ public byte A; public byte B;}一個包含 20 億個 TwoBytes的數組,
有了這些塊類型之後 ,GC
、如果一個方法裏引用了很多已經構造好的泛型數組類型,和 Span<T>一樣,這樣一來,但 ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<object>>>>就太大了
,一個 FourElements<T>數組的每個物理元素
,準確地說是 127.998 TiB
。所以 BigArray<T>保持普通數組的限製。ReadOnlySpan<T>、
所以第一個想法很簡單:讓一個數組元素代表多個邏輯元素
。而且分配用的輔助方法標記為 NoInlining