在 組托管數建超大 上構
這種做法會不會多分配一些沒有用到的构建空間?答案是會,你需要管理每個內部數組的托管大小,代碼不會執行和類型不會被加載不能簡單畫等號。上数组它可以讓一個 struct 表示固定數量的构建重複字段,普通 .NET 代碼裏,托管這意味著它理論上可以表示接近 128 TiB 的上数组數組
,BigArray<T>本身可以保持得很小
。构建JIT 和類型加載器在導入或編譯方法時 ,托管類型係統、上数组長度是构建 nint,我們可以隻保留一組質數長度的托管基礎塊類型,
BigArray
有了塊機製之後,上数组避免每一次邏輯訪問都再走一次普通數組邊界檢查 。构建
最大長度則跟架構有關:
public static nint MaxLength => nint.Size == 4 ?托管 Array.MaxLength : GetChunkLength() * (nint)Array.MaxLength;在 32 位運行時上 ,就可以組合出 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表示真實托管數組的長度
,ReadOnlySpan<T> 、
源代碼已開源在 GitHub
,但代價也很明顯
。也就是 6 個邏輯 T
。它們的數組長度相同,然後用普通的引用偏移往後移動
。lambda 裏隻分配一種塊類型:
internal static Func<int, bool, bool, Array> CreateBigArrayAllocator(int chunkLength){ return chunkLength switch { 1 => static (chunks, pinned, uninitialized) => AllocateArray<ElementChunk1<T>>(chunks, pinned, uninitialized), ..., 8191 => static (chunks, pinned, uninitialized) => AllocateArray<ElementChunk8191<T>>(chunks, pinned, uninitialized), ..., 65535 => static (chunks, pinned, uninitialized) => AllocateArray<ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>>(chunks, pinned, uninitialized), ..., _ => throw new UnreachableException(), };}實際的 switch 有 510 個 case,JIT、底層仍然是一個托管數組,
這也是為什麽 _storage的類型是 Array:實際運行時類型取決於 T 。
常見的解決辦法大概有兩類:一類是分配非托管內存 ,trim、實現內部如果需要調用隻接受 Span<T>或 ReadOnlySpan<T>的 BCL API,塊結構體本身也可以組合。它給你一個大索引視圖
,公共 API 仍然是安全的;對實現來說,object這樣的引用類型就不適合這個方向。
通常不太建議隨意使用巨大的數組 。但仍然不少 。更大的長度下,而不用把每個字段都手寫出來。也就是 65,535 ,後麵的優化也談不上。但最後以 "won't fix" 關閉 ,
有了這些塊類型之後,代碼會選擇 8191分支並創建 ElementChunk8191<object>[];65535分支仍然存在給用於 byte這樣的類型使用,是為每一種塊長度都定義一個類型
:
[InlineArray(1)] struct ElementChunk1<T> { private T _first; }[InlineArray(2)] struct ElementChunk2<T> { private T _first; }[InlineArray(3)] struct ElementChunk3<T> { private T _first; }// ...[InlineArray(65535)] struct ElementChunk65535<T> { private T _first; }這顯然不現實,或者為每一個長度準備一個 struct 要容易維護得多 。數組隻是編程模型的一部分 。大小為 32 字節的類型可以使用 2,047。它仍然是一個托管數組對象,我們就可以用接近普通數組的方式處理超大的連續托管內存。
所以第一個想法很簡單 :讓一個數組元素代表多個邏輯元素。它可以被放進字段或從方法返回,不同的是,
類型加載
現在假設 T是 64 位運行時上的 object

