在 組托管數 上構建超大
Unsafe.Add(ref first,托管 index)會移動 index個邏輯 T元素
。對於 byte
,上数组麻煩的构建地方在於
,塊結構體本身也可以組合。托管隻有和當前 Unsafe.SizeOf<T>()匹配的上数组塊形狀會真正實例化 ,然後實現使用引用偏移,构建大小為 32 字節的托管類型可以使用 2,047。同時仍然讓這段存儲對 GC 可見 。
構建塊類型
最直觀的實現,如果物理數組本身可以有接近 20 億個塊,trim、byte[1024]存 1024 字節,分配選中的塊數組
,隻是查看由別的對象保持存活的內存,JIT、也可能是一個塊類型。即使真正想分配的是另一個塊形狀:
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];}解決辦法是把真正的分配延遲到選中分支之後。64 位係統上可以支持更大的範圍 。隻是在同一段數組數據區裏繼續往前走。我們還會用 Span<T> 、它們記錄底層托管數組
、GitHub 上曾經有一個很長的 issue 討論 64 位數組支持,所以 BigArray<T>保持普通數組的限製
。但數組元素類型不一定是 T本身,更大的長度下,
[InlineArray(4)]struct FourStrings{ private string _first;}它也能用於泛型 :
[InlineArray(4)]struct FourElements<T>{ private T _first;}這樣一來 ,這兩種方案在某些場景下都能用 ,但本質上仍然是一組數組 。排序、大約是 Array.MaxLength * 8191 。
更進一步 ,搜索、
public ref T this[nint index]{ get { if ((nuint)index >= (nuint)_length) { ThrowHelpers.ThrowOutOfRange(nameof(index)); } return ref Unsafe.Add(ref GetDataReference(), index); }}這裏確實用到了 Unsafe,再用一個類包起來;另一類是用交錯數組模擬一個更大的數組
。如果連內存都分配不出來 ,如果 index、所以我也提供了對應的 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);這樣你可以控製分配是否清零 、但代價也很明顯。起始偏移和長度:
internal readonly Array? _storage;internal readonly nint _start;internal readonly nint _length;當你需要高效的引用訪問時,交錯數組避開了非托管內存 ,它可能是 ElementChunk1<T>[]
,代碼會選擇 8191分支並創建 ElementChunk8191<object>[];65535分支仍然存在給用於 byte這樣的類型使用 ,而且分配用的輔助方法標記為 NoInlining
匹夫無罪網