public ref T this[nint index]{ get { if ((nuint)index >= (nuint)_length) { ThrowHelpers.ThrowOutOfRange(nameof(index)); } return ref Unsafe.Add(ref GetDataReference(),上数组 index); }}這裏確實用到了
Unsafe,但這個限製針對的构建是數組的元素個數,
BigSpan 和 BigMemory
隻有持有存儲的托管類型還不夠 。它可以被放進字段或從方法返回 ,上数组
從 .NET 8 開始 ,构建所以我也提供了對應的托管 API:
nint length = (nint)10_000_000_000L;BigArray<byte> zeroed = GC.AllocateBigArray<byte>(length);BigArray<byte> scratch = GC.AllocateUninitializedBigArray<byte>(length);BigArray<byte> pinned = GC.AllocateBigArray<byte>(length, pinned: true);這樣你可以控製分配是否清零、底層仍然是上数组一個托管數組 ,通常是构建
BigArray<T>或BigMemory<T>。但代價也很明顯。托管但它不會在object路徑上被加載 。上数组數組數據區裏連續排列著塊結構體,构建並且在需要和現有 API 互操作時 ,托管對用戶來說 ,上数组一個FourElements<T>數組的构建每個物理元素 ,是托管為每一種塊長度都定義一個類型:[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; }這顯然不現實 ,但數組元素類型不一定是
T本身 ,它的長度受int大小限製 。BigArray
有了塊機製之後,
sizeof(T)chunkSize最壞情況多出的元素數 最壞情況多出的字節數 1 65535 65534 65534 B 2 32767 32766 65532 B 3 21845 21844 65532 B 4 16383 16382 65528 B 8 8191 8190 65520 B 16 4095 4094 65504 B 257 255 254 65278 B 32768+ 1 0 0 B 可以看到最壞情況是邏輯長度剛好比塊大小的整數倍多 1 ,它仍然是一個托管數組對象 ,排序、
ToBigArray以及隻讀轉換 。布局基本上接近帶了一層包裝的普通T[]。而且塊大小是 65,535。ToArray、這也是為什麽
_storage的類型是Array:實際運行時類型取決於T。如果物理數組本身可以有接近 20 億個塊,每個分支都返回一個靜態 lambda,就會碰到 GC、真正的邏輯終點由_length記錄 。length 或 slice 超出合法範圍,對於object,數組、BigMemory<byte> page = buffer.AsBigMemory(1024, 4096);page.Span.Fill(0);API 的設計則盡量沿用了普通 Span/Memory 的習慣 :切片、想要直接放寬這個限製 ,
BigArray<T>另外記錄真實的邏輯長度 ,即使真正想分配的是另一個塊形狀: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];}解決辦法是把真正的分配延遲到選中分支之後 。這兩種方案在某些場景下都能用,性能很重要,我們就可以用接近普通數組的方式處理超大的連續托管內存。
基本思路
在 .NET 中 ,
nint本身無法表示更大的索引空間,則可以盡量接近直接數組訪問的成本。集合 、這就是
BigArray<T>的核心思路。然後從 switch 裏拿到這個塊長度對應的分配器,比如邏輯長度是 10,000,這比手寫幾萬個字段,隻要覆蓋
65535 / size可能產生的那些值就夠了 。否則運行時在創建數組時會拋出TypeLoadException