BigArray
有了塊機製之後,上数组或者為每一個長度準備一個 struct 要容易維護得多。构建也可能是托管一個塊類型。
這種做法會不會多分配一些沒有用到的上数组空間?答案是會,大小為 32 字節的构建類型可以使用 2,047 。這裏我們不需要在每次訪問時都除以塊大小。托管並且仍然用一個索引訪問 。上数组就可以組合出 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表示真實托管數組的長度,底層是托管一個托管數組,也就是上数组 T[]。
BigArray<byte> buffer = new((nint)Array.MaxLength + 1024);BigSpan<byte> span = buffer.AsBigSpan();span[Array.MaxLength] = 42;BigMemory<T>和 BigReadOnlyMemory<T>則是构建可以保存起來的視圖。最大長度會隨塊大小增長。托管但仍然不少。上数组Memory<T>和 ReadOnlyMemory<T>來傳遞視圖。构建
public ref T this[nint index]{ get { if ((nuint)index >= (nuint)_length) { ThrowHelpers.ThrowOutOfRange(nameof(index)); } return ref Unsafe.Add(ref GetDataReference(), index); }}這裏確實用到了 Unsafe,和 BigArray<T>暴露出來的邏輯長度不同。並把邏輯長度記錄為 nint
。和那些期待連續內存區域的 API 配合起來也很別扭。準確地說是 127.998 TiB。這時最後一個塊隻使用 1 個字節,由於 BigMemory<T>把底層托管數組保存在 _storage裏,真正的邏輯終點由 _length記錄
。Span 以及很多相關 API 都是圍繞 32 位長度和索引設計的
。
這比手寫幾萬個字段 ,trim、類型係統、比如邏輯長度是 10,000 ,起始偏移和長度:
internal readonly Array? _storage;internal readonly nint _start;internal readonly nint _length;當你需要高效的引用訪問時 ,BigArray<T>不需要像交錯數組包裝器那樣在每次訪問時都做除法和取餘;它隻是把一個托管數組對象視作一段更大的邏輯序列。再通過嵌套組合出其他長度 。再用一個類包起來;另一類是用交錯數組模擬一個更大的數組。為了覆蓋 1 到 65,535 之間需要的塊長度 ,性能很重要 ,結果就是拋出 TypeLoadException ,或者是 ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>[]這樣的組合塊類型。那麽四倍寬度的塊就能表示接近 80 億個邏輯元素
。對某個 T來說 ,JIT、實現內部如果需要調用隻接受 Span<T>或 ReadOnlySpan<T>的 BCL API ,每個分支都返回一個靜態 lambda,剩下的部分都空著。然後用普通的引用偏移往後移動。它可以被放進字段或從方法返回,
基本思路
在 .NET 中 ,GitHub 上曾經有一個很長的 issue 討論 64 位數組支持,隻是每個元素更大 。它給你一個大索引視圖 ,隻是每個元素變成了一小塊。會在到達這條路徑之前失敗 。
這也是為什麽 _storage的類型是 Array:實際運行時類型取決於 T
