使用“非常非常”的大型数组

时间:2014-08-18 09:55:53

标签: c# .net .net-4.5

我需要使用非常大的小型数组(int或float数组),我只在具有大量ram的机器上定位X64,物理内存在我的场景中永远不会成为问题。在查看gcAllowVeryLargeObjects的doc时,我注意到了这一点:

•对于字节数组和单字节结构数组,任何单个维度的最大索引为2,147,483,591(0x7FFFFFC7),对于其他类型,最大索引为2,146,435,071(0X7FEFFFFF)。

现在我的问题是我实际上需要"要使用比这更大的阵列,这里适当的解决方法是什么?创建数组或其他抽象数组?

知道我主要需要顺序访问这些数组(从不随机读取,但通常不同的段通过不同的线程顺序读取,可能同时有100多个线程)我最好的选择是什么?

我可能需要保存最多65 536 000 000个或更多元素的数组。

7 个答案:

答案 0 :(得分:3)

如果你真的必须打破数组长度限制,那么你必须将数组拆分成适当大小的块。您可以将这些块一起包装在具有适当语义的容器中,例如James McCaffrey在一段时间之前发表博客的BigArrayOfLong对象。还有很多其他人喜欢它。

基本思想是使用锯齿状数组来分配您将要使用的空间。请注意,多维数组不会给你带来任何好处,因为它仍然是一个单独的对象,而锯齿状数组是一个较小的数组数组,每个数组都是它自己的对象(可能不是连续的)。

这是一个非常简单(而非特别优化)的实现:

public class HugeArray<T> : IEnumerable<T>
    where T : struct
{
    public static int arysize = (Int32.MaxValue >> 4) / Marshal.SizeOf<T>();

    public readonly long Capacity;
    private readonly T[][] content;

    public T this[long index]
    {
        get
        {
            if (index < 0 || index >= Capacity)
                throw new IndexOutOfRangeException();
            int chunk = (int)(index / arysize);
            int offset = (int)(index % arysize);
            return content[chunk][offset];
        }
        set
        {
            if (index < 0 || index >= Capacity)
                throw new IndexOutOfRangeException();
            int chunk = (int)(index / arysize);
            int offset = (int)(index % arysize);
            content[chunk][offset] = value;
        }
    }

    public HugeArray(long capacity)
    {
        Capacity = capacity;
        int nChunks = (int)(capacity / arysize);
        int nRemainder = (int)(capacity % arysize);

        if (nRemainder == 0)
            content = new T[nChunks][];
        else
            content = new T[nChunks + 1][];

        for (int i = 0; i < nChunks; i++)
            content[i] = new T[arysize];
        if (nRemainder > 0)
            content[content.Length - 1] = new T[nRemainder];
    }

    public IEnumerator<T> GetEnumerator()
    {
        return content.SelectMany(c => c).GetEnumerator();
    }

    IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

这个是静态分配的,但要使一个增长以适应需求并不太难。只需确保您指定的块大小不会完全超出范围。为了以防万一,我已根据项目大小进行了计算。

答案 1 :(得分:0)

您可以避免使用真实数组并通过流模拟它们。

如果您希望它可以搜索(您这样做),则限制为长(2 ^ 64/2(带符号)位) 然后你只需要索引* n个字节并读取n个字节。

如果使用int32或double(n = 4),则有2,8e +17个位置的空间。

答案 2 :(得分:0)

对于分布式计算来说,这听起来像是一个问题,比如Google Map Reduce。

当它对于您当前的基础架构来说太大时,请将其扩展到更多的框。

答案 3 :(得分:0)

嗯,我很确定你不能拥有一个大小为6500000000的数组,因为它比计算机内存要大(没有操作系统会给软件提供那么多内存。)可能还有其他原因。 如果由于某种原因你相信你可以得到那么多ram但你认为数组很小,你可以尝试使用基于链表的对象(比如堆栈甚至链表本身)。 链接列表不受索引数量的限制(如果它在你的ram范围内)

答案 4 :(得分:0)

我写这个作为解决方案,但希望有人能更好地提供我可以标记为已接受的答案。

一个解决方案,因为限制是在一个数组的维度上,将使用多维数组,并通过计算位置,就像它是一维数组一样简单地在多维数组中索引

//pseudocode
var index = some large number;
var index1 = index/sizeofarrays;
var index2 = index%sizeofarrays;
var data = myverylargemultidimentionalarray[index1,index2];

答案 5 :(得分:0)

我的建议是使用本机代码(即C ++ x64),因为C#不适合循环这么多元素。在尝试将大量数据加载到RAM之前,请仔细考虑从数据中提取所需的信息。

答案 6 :(得分:0)

听起来你应该使用一个流给我。只要在阅读完块后处理块,内存流就应该没问题。

我的猜测是,无论什么填充你的阵列运行速度比消耗它的速度快?如果是这种情况,您可以使用流只是一个缓冲区。当缓冲区达到临界质量时,阻止新条目,同时清除后退日志。听起来你已经有足够的记忆力而不是问题。

您的缓冲区内容可以以块的形式传递给并行库,并保留索引以提供当前索引。

伪代码:

  • 接收新项目并添加到超级内存流(内存将复制到页面文件,因此如果你还有疯狂的磁盘数量RAM就更不会出现问题了!)

TASK THREAD(为每个算法复制):

  • while buffer has items
  • 从缓冲区读取对象
  • 流程对象

如果要在每个任务中利用并行处理,首先流式传输一个对象块,然后将它们作为一个集合与起始索引一起传递给您的方法,这样您仍然可以推导出当前项目索引。