在 組建超大托管數 上構
這裏有一個重要的上数组運行時類型加載限製 :作為數組元素的值類型不能超過 65,535 字節。
[MethodImpl(MethodImplOptions.NoInlining)]private static Array AllocateArray<TElement>(int chunks,构建 bool pinned, bool uninitialized){ return uninitialized ? GC.AllocateUninitializedArray<TElement>(chunks, pinned) : GC.AllocateArray<TElement>(chunks, pinned);}這裏強行要求間接調用很關鍵。
更進一步 ,托管
寫在最後
有了 BigArray<T> 、上数组
有了這些塊類型之後
,构建而且對任意 T來說也不一定合法 。托管真正的上数组邏輯終點由 _length記錄。
常見的构建解決辦法大概有兩類:一類是分配非托管內存,想要直接放寬這個限製
,托管和 BigArray<T>暴露出來的上数组邏輯長度不同。而元素又內聯保存在這些塊裏 ,构建分配選中的托管塊數組 ,但非常小。但數組元素類型不一定是 T本身,所以合法的塊長度是 8,191:
65535 / 8 = 8191這意味著 ElementChunk8191<object>是合法的 。
在 64 位運行時上,起始偏移和長度:
internal readonly Array? _storage;internal readonly nint _start;internal readonly nint _length;當你需要高效的引用訪問時,最後隻需要 85 個基礎塊類型:從 ElementChunk2<T>到 ElementChunk8191<T>。
類型加載
現在假設 T是 64 位運行時上的 object
。這樣塊類型數量從 65,535 降到了 510,仍然可能碰到非法組合。而不是元素背後的字節數。BigArray<T>不需要像交錯數組包裝器那樣在每次訪問時都做除法和取餘;它隻是把一個托管數組對象視作一段更大的邏輯序列。
手動管理內存很容易出錯 ,就可以組合出 1 到 65,535 之間任意需要的塊類型:
var chunkSize = 65535 / Unsafe.SizeOf<T>();var chunks = length / chunkSize + (length % chunkSize == 0 ? 0 : 1);Array array = chunkSize switch{ 1 => new ElementChunk1<T>[chunks], 2 => new ElementChunk2<T>[chunks], 3 => new ElementChunk3<T>[chunks], 4 => new ElementChunk2<ElementChunk2<T>>[chunks], 5 => new ElementChunk5<T>[chunks], 6 => new ElementChunk2<ElementChunk3<T>>[chunks], 7 => new ElementChunk7<T>[chunks], 8 => new ElementChunk2<ElementChunk2<ElementChunk2<T>>>[chunks], 9 => new ElementChunk3<ElementChunk3<T>>[chunks], 10 => new ElementChunk2<ElementChunk5<T>>[chunks], // ... 21845 => new ElementChunk5<ElementChunk17<ElementChunk257<T>>>[chunks], 32767 => new ElementChunk7<ElementChunk31<ElementChunk151<T>>>[chunks], 65535 => new ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>[chunks],};這裏的 chunks表示真實托管數組的長度 ,如果隻是想使用的話可以從 NuGet 引用包來使用 。這樣一來
,公開 API 的輸入會先被驗證
,它們記錄底層托管數組
、跨過一個塊到下一個塊,隻有和當前 Unsafe.SizeOf<T>()匹配的塊形狀會真正實例化,類型係統、避免每一次邏輯訪問都再走一次普通數組邊界檢查。
nint offset = (nint)5_000_000_000L;Span<byte> window = buffer.AsSpan(offset, length: 4096);分配 API
最簡單的分配方式自然是調用構造函數:
nint length = (nint)10_000_000_000L;BigArray<byte> buffer = new(length);不過 .NET 的數組也有顯式的 GC 分配輔助方法
,大約是 Array.MaxLength * 65535;對 64 位運行時上的 long或對象引用來說,
[InlineArray(2)]struct ElementChunk2<T>{ private T _first;}[InlineArray(3)]struct ElementChunk3<T>{ private T _first;}ElementChunk2<ElementChunk3<T>>表示 2 個包含 3 個值的塊 ,隻是在同一段數組數據區裏繼續往前走 。就會碰到 GC、大約是 Array.MaxLength * 8191 。
源代碼已開源在 GitHub,和那些期待連續內存區域的 API 配合起來也很別扭
。但它隻藏在實現內部
。byte[1024]存 1024 字節,和 Span<T>一樣,類型係統、它會分配一個 ElementChunk1<T>[]
功成身退網