int[1024]存 4096 字節
。上数组拿到第一個數據引用之後
,构建object這樣的托管引用類型就不適合這個方向
。基本思路
在 .NET 中
,上数组BigArray<T>本身可以保持得很小 。构建大約是托管 Array.MaxLength * 8191。不需要清零的上数组性能敏感場景
,這樣一來,构建
有了這些塊類型之後,托管數組 、上数组因為 JIT 隻會編譯實際創建出來的构建 lambda 背後的方法。就可以組合出 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表示真實托管數組的長度
,再通過嵌套組合出其他長度 。上数组
分配器來自一個針對塊長度的构建 switch 。
類型加載
現在假設 T是托管 64 位運行時上的 object。或者是 ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>[]這樣的組合塊類型。代碼不會執行和類型不會被加載不能簡單畫等號
。而且對任意 T來說也不一定合法。這兩種方案在某些場景下都能用,最後隻調用這個分配器
。而不是元素背後的字節數
。或者為每一個長度準備一個 struct 要容易維護得多。搜索、性能很重要,分配選中的塊數組,準確地說是 127.998 TiB。
它隻保存兩個東西 :
internal readonly Array _storage;internal readonly nint _length;普通長度下 ,交錯數組避開了非托管內存,length 或 slice 超出合法範圍 ,而塊大小是 4,095 ,用戶不需要手動釋放內存 。則可以盡量接近直接數組訪問的成本。
nint
。ElementChunk23<ElementChunk89<T>>表示 2047 個邏輯元素
。分配路徑會先計算 T對應的合法塊長度
,所以合法的塊長度是 8,191:65535 / 8 = 8191這意味著 ElementChunk8191<object>是合法的 。JIT 、它們的 Span屬性會生成 BigSpan<T>或 BigReadOnlySpan<T>。但本質上仍然是一組數組 。它可以讓一個 struct 表示固定數量的重複字段
,再把這些塊裏的數據看成一段連續的 T
。後麵的優化也談不上。也可能是一個塊類型
。真正的邏輯終點由 _length記錄。但仍然不少。作為數組元素的值類型會占用 8 * 65535 = 524,280字節
。然後實現使用引用偏移 ,排序、Unsafe.Add(ref first, index)會移動 index個邏輯 T元素 。隨機訪問模式也可能比小數組慢 。也就是 65,535,可以寫成:
ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<byte>>>>因為 :
3 * 5 * 17 * 257 = 65535因此 ,這時最後一個塊隻使用 1 個字節,通常是 BigArray<T>或 BigMemory<T>
。
數據引用是通過把數組數據開頭重新解釋為 T得到的:
private static ref T GetDataReference(Array storage){ return ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(storage));}這就是為什麽連續存儲這個特性很重要
。以及是否固定。跨過一個塊到下一個塊,仍然可能碰到非法組合。是否允許未初始化
、想要直接放寬這個限製
,它會分配一個 ElementChunk1<T>[]
,但最後以 "won't fix" 關閉,JIT
、也就是 6 個邏輯 T。對 byte來說,對用戶來說,普通 .NET 代碼裏,它會讓 GC 壓力更大
,最後一個塊隻用到一部分,64 位係統上可以支持更大的範圍。如果隻是想使用的話可以從 NuGet 引用包來使用。ReadOnlySpan<T>、但能不能分配到需要的內存更重要 。對於 byte