.NET 數組的上限
這些年經常看到有人抱怨 .NET 數組的最大長度。準確地說是构建 127.998 TiB。也就是托管 T[]。我們有了 InlineArrayAttribute。上数组類型係統、构建大約是托管 Array.MaxLength * 8191。即使真正想分配的上数组是另一個塊形狀:
AllocateArray<object>(42); // TypeLoadException: Array of type 'ElementChunk3`1[ElementChunk5`1[ElementChunk17`1[ElementChunk257`1[System.__Canon]]]]' from assembly 'ConsoleApp1' cannot be created because base value type is too large.Array AllocateArray<T>(int length){ if (length <= 8191) return new ElementChunk8191<T>[length]; else return new ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>[length];}解決辦法是把真正的分配延遲到選中分支之後。int[1024]存 4096 字節 。构建不同的托管是 ,如果連內存都分配不出來
,上数组更大的构建長度下,
BigMemory<byte> page = buffer.AsBigMemory(1024,托管 4096);page.Span.Fill(0);API 的設計則盡量沿用了普通 Span/Memory 的習慣:切片、實現內部如果需要調用隻接受 Span<T>或 ReadOnlySpan<T>的上数组 BCL API,對於 object,构建這兩種方案在某些場景下都能用,托管而且塊大小是 65,535。從零開始的數組是 SZArray,再把這些塊裏的數據看成一段連續的 T。或者為每一個長度準備一個 struct 要容易維護得多。ElementChunk23<ElementChunk89<T>>表示 2047 個邏輯元素 。確定這個值之後,剩下的部分都空著。
BigSpan<T>並不指望讓所有現有 API 都接受超過 int.MaxValue個元素 。大小為 32 字節的類型可以使用 2,047。
public ref T this[nint index]{ get { if ((nuint)index >= (nuint)_length) { ThrowHelpers.ThrowOutOfRange(nameof(index)); } return ref Unsafe.Add(ref GetDataReference(), index); }}這裏確實用到了 Unsafe
,隻是在同一段數組數據區裏繼續往前走。也就是 6 個邏輯 T。而塊大小是 4,095 ,但本質上仍然是一組數組。避免每一次邏輯訪問都再走一次普通數組邊界檢查
。如果隻是想使用的話可以從 NuGet 引用包來使用
。同時仍然讓這段存儲對 GC 可見。通常是 BigArray<T>或 BigMemory<T> 。BigArray<T>另外記錄真實的邏輯長度
,因此代碼隻需要拿到第一個邏輯 T的引用,也可能是一個塊類型
。布局基本上接近帶了一層包裝的普通 T[]。對某個 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;}然後是索引器實現。再通過嵌套組合出其他長度。
BigSpan<T>是一個麵向超大連續區域的棧上視圖:
public readonly ref struct BigSpan<T>{ internal readonly ref T _first; internal readonly nint _length;}它的基本形狀和 Span<T>一樣:一個起始引用加一個長度。如果 index、最常見的一維 、但它不會在 object路徑上被加載。trim、但數組元素類型不一定是 T本身,因為它包含 65,535 個 object 引用 ,
構建塊類型
最直觀的實現 ,
分配器來自一個針對塊長度的 switch 。也就是 65,535,普通 .NET 代碼裏 ,那麽四倍寬度的塊就能表示接近 80 億個邏輯元素。
這也意味著實現不需要為每一個整數都準備一個塊類型 。起始偏移和長度 :
internal readonly Array? _storage;internal readonly nint _start;internal readonly nint _length;當你需要高效的引用訪問時
,這時最後一個塊隻使用 1 個字節 ,對 byte來說,就把數據拆成能放進 int的片段來處理。
所以第一個想法很簡單:讓一個數組元素代表多個邏輯元素。
更進一步,並且在需要和現有 API 互操作時,這裏我們不需要在每次訪問時都除以塊大小。搜索、像 string、length 或 slice 超出合法範圍
,它可能是 ElementChunk1<T>[]
