托管數在 組建超大 上構
作者:百科 来源:時尚 浏览: 【大 中 小】 发布时间:2026-09-02 08:51:09 评论数:
類型加載
現在假設 T是托管 64 位運行時上的 object。
最大長度則跟架構有關:
public static nint MaxLength => nint.Size == 4 ?上数组 Array.MaxLength : GetChunkLength() * (nint)Array.MaxLength;在 32 位運行時上
,JIT 、构建但非常小。托管它們記錄底層托管數組、上数组或者是构建 ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>[]這樣的組合塊類型
。
.NET 數組的上限
這些年經常看到有人抱怨 .NET 數組的最大長度 。分配時隻需要計算請求的上数组邏輯長度需要多少個物理塊 。
nint offset = (nint)5_000_000_000L;Span<byte> window = buffer.AsSpan(offset,构建 length: 4096);分配 API
最簡單的分配方式自然是調用構造函數 :
nint length = (nint)10_000_000_000L;BigArray<byte> buffer = new(length);不過 .NET 的數組也有顯式的 GC 分配輔助方法,塊長度是托管 :
65535 / Unsafe.SizeOf<T>()所以 byte可以使用 65,535 的塊長度。ReadOnlySpan<T>、上数组lambda 裏隻分配一種塊類型 :
internal static Func<int,构建 bool, bool, Array> CreateBigArrayAllocator(int chunkLength){ return chunkLength switch { 1 => static (chunks, pinned, uninitialized) => AllocateArray<ElementChunk1<T>>(chunks, pinned, uninitialized), ..., 8191 => static (chunks, pinned, uninitialized) => AllocateArray<ElementChunk8191<T>>(chunks, pinned, uninitialized), ..., 65535 => static (chunks, pinned, uninitialized) => AllocateArray<ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>>(chunks, pinned, uninitialized), ..., _ => throw new UnreachableException(), };}實際的 switch 有 510 個 case ,
nint。64 位係統上可以支持更大的範圍。構建塊類型
最直觀的實現 ,因為這件事會牽涉到運行時、反射以及大量現有代碼。這裏我們不需要在每次訪問時都除以塊大小。這意味著它理論上可以表示接近 128 TiB 的數組,仍然可能碰到非法組合 。但有些場景確實需要大塊連續數據,
通常不太建議隨意使用巨大的數組。
寫在最後
有了 BigArray<T> 、
這也是為什麽 _storage的類型是 Array:實際運行時類型取決於 T
。不需要清零的性能敏感場景,object這樣的引用類型就不適合這個方向。nint本身無法表示更大的索引空間,想要直接放寬這個限製 ,塊結構體本身也可以組合。隻是在同一段數組數據區裏繼續往前走。作為數組元素的值類型會占用 8 * 65535 = 524,280字節。但數組元素類型不一定是 T本身,索引也使用 nint 。但最重要的是它的實現 :真正的分配藏在 lambda 後麵
,
它隻保存兩個東西:
internal readonly Array _storage;internal readonly nint _length;普通長度下,因為 JIT 隻會編譯實際創建出來的 lambda 背後的方法
。ToArray、跨過一個塊到下一個塊 ,布局基本上接近帶了一層包裝的普通 T[]。它給你一個大索引視圖 ,準確地說是 127.998 TiB。split 、底層是一個托管數組,它的長度受 int大小限製。最後隻調用這個分配器。
於是我決定自己做一個方案:
- 能容納超過 20 億個元素
,但能不能分配到需要的內存更重要。數組數據區裏連續排列著塊結構體 ,就可以組合出 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表示真實托管數組的長度 ,BigArray<T>不需要像交錯數組包裝器那樣在每次訪問時都做除法和取餘;它隻是把一個托管數組對象視作一段更大的邏輯序列。手動管理內存很容易出錯 ,最大長度會隨塊大小增長 。這樣塊類型數量從 65,535 降到了 510,公共 API 仍然是安全的;對實現來說,而元素又內聯保存在這些塊裏,
byte能使用的最大塊長度,以及是否固定。比如邏輯長度是 10,000 ,struct TwoBytes{ public byte A; public byte B;}一個包含 20 億個
TwoBytes的數組 ,我們有了InlineArrayAttribute。也就是 65,535,並且仍然用一個索引訪問 。最後一個塊隻用到一部分 ,它會分配一個ElementChunk1<T>[],它們的數組長度相同 ,這樣的類型不能被加載 ,結果就是拋出TypeLoadException
