数组在. net 4.0中共享内存-是否可以通过反射或StructLayout实现



我快速创建了巨大的瞬态数组。有些是保留的,有些是GC-d。这将对堆进行碎片整理,应用程序将消耗大约。比实际需要的内存多2.5倍,导致OutOfMemoryException。

作为一种解决方案,我宁愿有一个巨大的数组(PointF[]),并自己做段的分配和管理。但是我想知道如何使两个(或更多)数组共享相同的内存空间。

PointF[] giganticList = new PointF[100];
PointF[] segment = ???; 
// I want the segment length to be 20 and starting e.g at position 50 
// within the gigantic list

我在想一个技巧,就像这个问题的赢家答案。这可能吗?问题是片段数组的长度和数量只有在运行时才知道。

假设您确定可以避免OutOfMemoryException,并且您将其全部放在内存中的方法并不是实际问题(如果内存可用,GC非常擅长阻止这种情况发生)…

  • 这是第一个问题。我不确定CLR是否支持大于2 GB的单个对象。
    • 关键编辑-gcAllowVeryLargeObjects在64位系统上更改此-在推出自己的解决方案之前先尝试一下。
  • 其次,你说的是"有些被保留,有些被GC"。也就是说,一旦你完成了"子数组",你希望能够重新分配数组的元素。
  • 第三,我假设你问题中的PointF[] giganticList = new PointF[100];更像PointF[] giganticList = new PointF[1000000];?

还可以考虑使用MemoryFailPoint,因为它允许您"要求"内存并检查异常,而不是使用OutOfMemoryException崩溃。

编辑也许最重要的是,你现在进入了一个权衡取舍的领域。如果你这样做,你可能会开始失去诸如在循环开始时通过做数组绑定检查来优化for循环的抖动等优点(for (int i= 0; i < myArray.Length; i++) 得到优化,int length = 5; for (int i= 0; i < length; i++)没有)。如果您有高计算资源代码,那么这可能会对您造成伤害。您还必须更加努力地并行处理不同的子数组。创建子数组的副本,或它们的部分,甚至其中的项,仍然会分配更多的内存,这些内存将被GC。

这可以通过包装数组,并跟踪哪个部分用于哪个子数组来实现。你实际上是在谈论分配一个巨大的内存块,然后重用其中的一部分,而不把责任放在GC上。您可以利用ArraySegment<T>,但这也有它自己的潜在问题,比如将原始数组暴露给所有调用者。

这并不简单,但它是可能的。可能不是每次删除子数组时,您都希望通过移动其他子数组来关闭间隙来对主数组进行碎片整理(或者当您用光了连续的段时这样做)。

一个简单的例子看起来像下面的伪代码(未经测试,如果你的电脑离开家,你的猫爆炸了,不要怪我)。还有另外两种方法,我在最后提到了。

public class ArrayCollection {
List<int> startIndexes = new List<int>();
List<int> lengths = new List<int>();
const int 1beeellion = 100;
PointF[] giganticList = new PointF[1beeellion];    
public ArraySegment<PointF> this[int childIndex] {
get {
// Care with this method, ArraySegment exposes the original array, which callers could then
//  do bad things to
return new ArraySegment<String>(giganticList, startIndexes[childIndex], length[childIndex]);
}}
// returns the index of the child array
public int AddChild(int length) {
// TODO: needs to take account of lists with no entries yet
int startIndex = startIndexes.Last() + lengths.Last(); 
// TODO: check that startIndex + length is not more than giganticIndex
//   If it is then 
//     find the smallest unused block which is larger than the length requested
//     or defrag our unused array sections
//   otherwise throw out of memory
startIndexes.Add(startIndex); // will need inserts for defrag operations
lengths.Add(length); // will need inserts for defrag operations
return startIndexes.Count - 1; // inserts will need to return inserted index
}      
public ArraySegment<PointF> GetChildAsSegment(int childIndex) {
// Care with this method, ArraySegment exposes the original array, which callers could then
//  do bad things to
return new ArraySegment<String>(giganticList, startIndexes[childIndex], length[childIndex]);
}
public void SetChildValue(int childIndex, int elementIndex, PointF value) {
// TODO: needs to take account of lists with no entries yet, or invalid childIndex
// TODO: check and PREVENT buffer overflow (see warning) here and in other methods
//    e.g. 
if (elementIndex >= lengths[childIndex]) throw new YouAreAnEvilCallerException();
int falseZeroIndex = startIndexes[childIndex];
giganticList[falseZeroIndex + elementIndex];
}
public PointF GetChildValue(int childIndex, int elementIndex) {
// TODO: needs to take account of lists with no entries yet, bad child index, element index
int falseZeroIndex = startIndexes[childIndex];
return giganticList[falseZeroIndex + elementIndex];
}
public void RemoveChildArray(int childIndex) {
startIndexes.RemoveAt(childIndex);
lengths.RemoveAt(childIndex);
// TODO: possibly record the unused segment in another pair of start, length lists
// to allow for defraging in AddChildArray
}
}

警告以上代码有效地引入了缓冲区溢出漏洞例如,如果您没有在SetChildValue等方法中的子数组中检查所请求的childIndexlength。在尝试在生产环境中执行此操作之前,您必须了解这一点并防止它,特别是在将这些方法与unsafe结合使用时。

现在,这可以扩展为返回子数组的psuedo indexpublic PointF this[int index]方法,子数组的枚举器等,但正如我所说,这变得越来越复杂,你需要决定它是否真的能解决你的问题。大部分时间将花费在重用(第一个)碎片整理(第二个)扩展(第三个)throw OutOfMemory(最后一个)逻辑上。

如果我关于2GB对象限制的评论是正确的,那么这种方法还有一个优点,即您可以分配许多2GB的子数组并将它们用作单个数组。

这里假设您不想走unsafe路线并使用指针,但效果是一样的,您只需创建一个包装器类来管理固定内存块中的子数组。

另一种方法是使用哈希集/字典方法。分配整个数组(巨大的2GB数组)并将其分成块(例如100个数组元素)。然后,子数组将有多个块分配给它,并且在其最终块中浪费一些空间。这将产生一些总体空间浪费的影响(取决于您的平均"子长度vs。块长度"预测"),但优点是您可以增加和减少子数组的大小,以及在对碎片影响较小的情况下删除和插入子数组。


值得注意引用:

  • 64位。net 4:gcAllowVeryLargeObjects
  • MemoryFailPoint-允许您"要求"内存并检查异常,而不是在事实后与OutOfMemoryException崩溃
  • Large Arrays和LOH Fragmentation。什么是公认的惯例?
  • 3GB进程限制在32位,参见:3_GB_barrier, Server Fault/3GB considerations和AWE/PAE
  • 缓冲区溢出漏洞,以及为什么你可以在c#中得到这个

作为不同类型的数组或结构访问数组的其他示例。这些实现可以帮助您开发自己的解决方案

<<ul>
  • BitArray类/gh><<li>将来预留结构/gh>
  • n泛型——在本库的一些成员中巧妙地使用了类似数组的概念,特别是一般的结构,如ObjectMatrix和Bag
  • c#对象数组,非常大,寻找更好的方法
  • 阵列优化

    • Eric Gu -数组的迭代效率?-注意它的年代,但是如何寻找JIT优化的方法仍然与。net 4.0相关(参见CLR中的数组边界检查消除?例如)
    • Dave delefs -数组边界检查消除在CLR
    • 警告- pdf: 64位架构的隐式数组边界检查
    • LinkedList -允许你在一个序列中引用多个不同的数组桶(在块桶方法中将块绑在一起)

    并行数组和unsafe

    的使用
    • 并行矩阵乘法使用任务并行库(TPL),特别是UnsafeSingle-由单个数组表示的方形或锯齿数组是您试图解决的同类问题。
    • 缓冲区溢出漏洞,以及为什么你可以在c#中得到这个(是的,我已经提到过三次了,这很重要)

    您最好的选择可能是在同一个PointF[]实例上使用多个ArraySegment<PointF>,但在不同的偏移量,并让您的调用代码注意相对的.Offset.Count。请注意,您必须编写自己的代码来分配下一个块,并查找间隙等-本质上是您自己的迷你分配器。

    不能直接将视为PointF[]

    :

    PointF[] giganticList = new PointF[100];
    // I want the segment length to be 20 and starting e.g at position 50 
    // within the gigantic list
    var segment = new ArraySegment<PointF>(giganticList, 50, 20);
    

    作为旁注:另一种方法可能是使用指针指向数据-无论是来自非托管分配,还是来自已固定的托管数组(注意:您应该尽量避免固定),但是:虽然PointF*可以传递其自己的偏移信息,但它不能传递长度-因此您需要始终传递PointF*Length。当您这样做的时候,您可能已经使用了ArraySegment<T>,它的附带好处是不需要任何unsafe代码。当然,根据场景的不同,将大数组视为非托管内存可能(在某些场景中)仍然是诱人的。

    相关内容

    • 没有找到相关文章

    最新更新