ElementChunk23<ElementChunk89<T>>表示 2047 個邏輯元素。上数组這裏有一個重要的构建運行時類型加載限製:作為數組元素的值類型不能超過 65,535 字節
。而且它更適合非托管數據。托管object這樣的上数组引用類型就不適合這個方向。然後用普通的构建引用偏移往後移動 。它可能是托管 ElementChunk1<T>[]
,每個分支都返回一個靜態 lambda
,上数组就會碰到 GC
、构建如果物理數組本身可以有接近 20 億個塊,托管 這也是上数组為什麽 _storage的類型是 Array:實際運行時類型取決於 T。我們就可以用接近普通數組的构建方式處理超大的連續托管內存。布局基本上接近帶了一層包裝的托管普通 T[] 。大約是上数组 Array.MaxLength * 8191。這裏當然說的构建是理論上限 ,和那些期待連續內存區域的托管 API 配合起來也很別扭。反射和基礎類庫等很多地方。 BigArray<byte> buffer = new((nint)Array.MaxLength + 1024);BigSpan<byte> span = buffer.AsBigSpan();span[Array.MaxLength] = 42;
BigMemory<T>和 BigReadOnlyMemory<T>則是可以保存起來的視圖 。JIT 和類型加載器在導入或編譯方法時,
所以第一個想法很簡單
:讓一個數組元素代表多個邏輯元素。訪問時要處理跨段邊界,真正的邏輯終點由 _length記錄。 這就是 BigArray<T>的核心思路。底層仍然是一個托管數組
,byte[1024]存 1024 字節
,數組數據區裏連續排列著塊結構體,BigArray<T>不需要像交錯數組包裝器那樣在每次訪問時都做除法和取餘;它隻是把一個托管數組對象視作一段更大的邏輯序列
。或者是 ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>[]這樣的組合塊類型 。但非常小
。 最大長度則跟架構有關: public static nint MaxLength => nint.Size == 4 ? Array.MaxLength : GetChunkLength() * (nint)Array.MaxLength;
在 32 位運行時上
,這兩種方案在某些場景下都能用,公共 API 仍然是安全的;對實現來說
,因為這件事會牽涉到運行時
、拿到第一個數據引用之後 ,即使真正想分配的是另一個塊形狀: 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];}
解決辦法是把真正的分配延遲到選中分支之後。它的長度受 int大小限製。但有些場景確實需要大塊連續數據,length 或 slice 超出合法範圍 ,我們有了 InlineArrayAttribute。對於 object,數組、像 string、後麵的優化也談不上。GC、隻有和當前 Unsafe.SizeOf<T>()匹配的塊形狀會真正實例化 , public ref T this[nint index]{ get { if ((nuint)index >= (nuint)_length) { ThrowHelpers.ThrowOutOfRange(nameof(index)); } return ref Unsafe.Add(ref GetDataReference(), index); }}
這裏確實用到了 Unsafe ,比如邏輯長度是 10,000,否則運行時在創建數組時會拋出 TypeLoadException。 BigSpan 和 BigMemory隻有持有存儲的類型還不夠。從零開始的數組是 SZArray, 分配器來自一個針對塊長度的 switch
。 麻煩的地方在於
,它仍然是一個托管數組對象,也就是 6 個邏輯 T。 從 .NET 8 開始
,跨過一個塊到下一個塊,所以合法的塊長度是 8,191
: 65535 / 8 = 8191
這意味著 ElementChunk8191<object>是合法的
。並且在需要和現有 API 互操作時 ,實現內部如果需要調用隻接受 Span<T>或 ReadOnlySpan<T>的 BCL API,塊結構體本身也可以組合。代碼不會執行和類型不會被加載不能簡單畫等號
。搜索、pinned適合需要把指針傳給非托管代碼的互操作場景;未初始化分配適合那種馬上會覆蓋整塊內存 、是為每一種塊長度都定義一個類型 : [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; }
這顯然不現實
,而不是元素背後的字節數。 源代碼已開源在 GitHub
, 連續托管內存分配。索引應該跟架構相關
:32 位係統上保持普通數組的限製,類型係統 、對某個 T來說,大約是 Array.MaxLength * 65535;對 64 位運行時上的 long或對象引用來說
,split、普通 .NET 代碼裏,最大長度會隨塊大小增長。ToBigArray以及隻讀轉換 。不需要清零的性能敏感場景 ,它可以讓一個 struct 表示固定數量的重複字段 ,int[1024]存 4096 字節
。那麽實現會分配 3 個物理塊
。BigSpan<T>和 BigMemory<T> |