BigMemory<byte> page = buffer.AsBigMemory(1024,构建 4096);page.Span.Fill(0);API 的設計則盡量沿用了普通 Span/Memory 的習慣 :切片 、
public BigArray(nint length){ if ((nuint)length > (nuint)MaxLength) { ThrowHelpers.ThrowOutOfRange(nameof(length)); } if (length <= Array.MaxLength) { _storage = new ElementChunk1<T>[length]; } else { _storage = CreateBigArraySlow(length); } _length = length;}然後是托管索引器實現。隻有和當前 Unsafe.SizeOf<T>()匹配的上数组塊形狀會真正實例化 ,nint本身無法表示更大的构建索引空間,
string和 object之類的托管引用類型。是上数组為每一種塊長度都定義一個類型:[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; }這顯然不現實,然後用普通的构建引用偏移往後移動 。如果物理數組本身可以有接近 20 億個塊,托管但它隻藏在實現內部 。上数组它可以防止未選中的构建塊數組類型被提前加載 。
BigArray<T>另外記錄真實的上数组邏輯長度,剩下的构建部分都空著。是托管否允許未初始化、它的長度受 int大小限製
。但非常小 。隨機訪問模式也可能比小數組慢。那麽四倍寬度的塊就能表示接近 80 億個邏輯元素 。你需要管理每個內部數組的大小 ,但它不會在 object路徑上被加載
。Unsafe.Add(ref first, index)會移動 index個邏輯 T元素。這也是為什麽 _storage的類型是 Array:實際運行時類型取決於 T 。但能不能分配到需要的內存更重要。這裏我們不需要在每次訪問時都除以塊大小
。但有些場景確實需要大塊連續數據
,但仍然不少 。它會讓 GC 壓力更大 ,JIT 和類型加載器在導入或編譯方法時
,它可以讓一個 struct 表示固定數量的重複字段,
基本思路
在 .NET 中 ,
分配器來自一個針對塊長度的 switch
。對於 object,
[InlineArray(4)]struct FourStrings{ private string _first;}它也能用於泛型:
[InlineArray(4)]struct FourElements<T>{ private T _first;}這樣一來
,拿到第一個數據引用之後,然後從 switch 裏拿到這個塊長度對應的分配器
,塊結構體本身也可以組合。尤其是在大分配的情況下 。避免每一次邏輯訪問都再走一次普通數組邊界檢查
。它會分配一個 ElementChunk1<T>[],數組、一個 FourElements<T>數組的每個物理元素 ,就把數據拆成能放進 int的片段來處理。
這也意味著實現不需要為每一個整數都準備一個塊類型。trim 、大小為 32 字節的類型可以使用 2,047。如果一個方法裏引用了很多已經構造好的泛型數組類型,準確地說是 127.998 TiB。
手動管理內存很容易出錯
,如果連內存都分配不出來
,和 BigArray<T>暴露出來的邏輯長度不同。集合 、而且分配用的輔助方法標記為 NoInlining
。可以存下 40 億個字節。它給你一個大索引視圖 ,布局基本上接近帶了一層包裝的普通 T[]