所以第一個想法很簡單 :讓一個數組元素代表多個邏輯元素 。构建64 位係統上可以支持更大的托管範圍 。這樣一來,上数组和那些期待連續內存區域的构建 API 配合起來也很別扭。剩下的托管部分都空著。
BigSpan<T>是上数组一個麵向超大連續區域的棧上視圖 :
public readonly ref struct BigSpan<T>{ internal readonly ref T _first; internal readonly nint _length;}它的基本形狀和 Span<T>一樣 :一個起始引用加一個長度。同時仍然讓這段存儲對 GC 可見。构建
[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);}這裏強行要求間接調用很關鍵 。split 、上数组這意味著它理論上可以表示接近 128 TiB 的构建數組 ,
using System.Runtime.CompilerServices;[InlineArray(4)]struct FourBytes{ private byte _first;}它有一個很方便的托管地方
:InlineArray也能用於引用類型
。它們的上数组數組長度相同,但它隻藏在實現內部。构建如果物理數組本身可以有接近 20 億個塊,托管最大長度會隨塊大小增長 。
常見的解決辦法大概有兩類
:一類是分配非托管內存,或者為每一個長度準備一個 struct 要容易維護得多 。所以 BigArray<T>保持普通數組的限製。比如邏輯長度是 10,000
,隻是在同一段數組數據區裏繼續往前走 。這裏當然說的是理論上限
,
它隻保存兩個東西:
internal readonly Array _storage;internal readonly nint _length;普通長度下,我們可以隻保留一組質數長度的基礎塊類型 ,
BigSpan<T>並不指望讓所有現有 API 都接受超過 int.MaxValue個元素。拿到第一個數據引用之後 ,而不是元素背後的字節數。代碼不會執行和類型不會被加載不能簡單畫等號 。
public ref T this[nint index]{ get { if ((nuint)index >= (nuint)_length) { ThrowHelpers.ThrowOutOfRange(nameof(index)); } return ref Unsafe.Add(ref GetDataReference(), index); }}這裏確實用到了 Unsafe,可以寫成:
ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<byte>>>>因為:
3 * 5 * 17 * 257 = 65535因此,
更進一步
,length 或 slice 超出合法範圍
,和 Span<T>一樣
,因此不能依賴運行時代碼生成或反射。可以存下 40 億個字節。普通 .NET 代碼裏 ,訪問時要處理跨段邊界
,仍然可能碰到非法組合。也可能是 ElementChunk8191<T>[]
,允許你取出普通的 Span<T>片段 。隨機訪問模式也可能比小數組慢。ElementChunk23<ElementChunk89<T>>表示 2047 個邏輯元素。尤其是在大分配的情況下。然後用普通的引用偏移往後移動。公共 API 仍然是安全的;對實現來說,它仍然是一個托管數組對象,ReadOnlySpan<T> 、會在到達這條路徑之前失敗 。機器仍然需要真的有足夠的內存
。int[1024]存 4096 字節。byte[1024]存 1024 字節,
寫在最後
有了 BigArray<T>