题 在x86上处理非常大的列表


我需要使用大型浮动列表,但我在x86系统上遇到了内存限制。我不知道最终长度,所以我需要使用可扩展类型。在x64系统上,我可以使用 <gcAllowVeryLargeObjects>

我目前的数据类型:

List<RawData> param1 = new List<RawData>();
List<RawData> param2 = new List<RawData>();
List<RawData> param3 = new List<RawData>();

public class RawData
{
    public string name;
    public List<float> data;
}

paramN列表的长度很低(当前为50或更低),但数据可以是10m +。当长度为50时,我达到内存限制(OutOfMemoryException)在刚好超过1米的数据点,当长度为25时,我在2米以上的数据点达到极限。 (如果我的计算是正确的,那就是200MB,加上名称的大小加上开销)。我可以用什么来增加这个限制?

编辑: 我试过用 List<List<float>> 最大内部列表大小为1 << 17(131072),这有点增加了限制,但仍然没有达到我想要的程度。

EDIT2: 我尝试将List>中的块大小减少到8192,并且我得到了大约2.3m元素的OOM,任务管理器读取了大约1.4GB的进程。看起来我需要减少数据源和存储之间的内存使用,或者更频繁地触发GC - 我能够在具有4GB RAM的PC上的x64进程中收集10m数据点,IIRC进程从未超过3GB

EDIT3: 我将代码缩减为处理数据的部分。 http://pastebin.com/maYckk84

Edit4: 我看了一下DotMemory,发现我的数据结构确实占用了我正在测试的设置~1GB(50ch * 3 params * 2m events = 300,000,000个float元素)。我想我需要在x86上限制它,或者在获取数据时弄清楚如何以这种格式写入磁盘


11
2017-07-18 00:10


起源


你的问题是......? - ZivS
hitting memory limits 就像得到一样的东西 OutOfMemoryException? 200MB是你期望每个List中的每个项目占用多少? - p e p
当达到限制时,列表容量大小调整算法将保持阵列的大小加倍。这可能证明非常低效。是否可以预测任何列表的最终长度,从而提供施工能力?如果您的任何列表在没有修改的情况下闲置任何可观的时间长度,您应该考虑使用 .TrimExcess(),但要注意之后的单一添加 .TrimExcess 将导致容量翻倍。 - spender
您需要使用一种数据结构,将数据存储在纯连续数据之外的其他数据中 List<T> 在内部使用数组)。您可能希望创建一个自定义数据结构,在现有数组填满时创建新数组,将它们像链接列表一样菊花链。互联网 StringBuilder 这是因为.Net 4.0或4.5,因此您可以查看其源代码以获取示例。 - Gjeltema
您的代码示例未充分说明确切的问题。一般来说,你可能会达到a的大小限制 List<T> 在内存不足之前,你可以通过创建一个数据结构来解决这个问题。一个 List<List<T>> (即列表清单)。但是在x86上,你将始终严格限制在相对较少的数据量(3GB是理论上的最大值,但实际上实际限制可以低至1.2-1.4GB)。提供 一个好的, 最小, 完成 代码示例 如果你想要一个真正的答案,可靠地再现问题。 - Peter Duniho


答案:


首先,在x86系统上,内存限制为2GB,而不是200MB。我相信 你的问题比那更棘手。你有积极的LOH(大对象堆)碎片。
CLR对小型和大型对象使用不同的堆。如果对象的大小大于85,000字节,则该对象很大。 LOH是一个非常棘手的事情,它并不急于将未使用的内存返回给操作系统,并且它在碎片整理方面非常差。
.Net List是ArrayList数据结构的实现,它将元素存储在数组中,它具有固定的大小;填充数组时,将创建具有双倍大小的新数组。随着您的数据量不断增长的阵列是LOH的“饥饿”场景。
因此,您必须使用量身定制的数据结构来满足您的需求。例如。块的列表,每个块都足够小,不会进入LOH。这是一个小原型:

public class ChunkedList
{
    private readonly List<float[]> _chunks = new List<float[]>();
    private const int ChunkSize = 8000;
    private int _count = 0;       

    public void Add(float item)
    {            
        int chunk = _count / ChunkSize;
        int ind = _count % ChunkSize;
        if (ind == 0)
        {
            _chunks.Add(new float[ChunkSize]);
        }
        _chunks[chunk][ind] = item;
        _count ++;
    }

    public float this[int index]
    {
        get
        {
            if(index <0 || index >= _count) throw new IndexOutOfRangeException();
            int chunk = index / ChunkSize;
            int ind = index % ChunkSize;
            return _chunks[chunk][ind];
        }
        set
        {
            if(index <0 || index >= _count) throw new IndexOutOfRangeException();
            int chunk = index / ChunkSize;
            int ind = index % ChunkSize;
            _chunks[chunk][ind] = value;
        }
    }
    //other code you require
}

ChunkSize = 8000每个块只需要32,000个字节,因此它不会进入LOH。 _chunks 只有当收集到大约16,000个块时才会进入LOH,这将超过1.28亿个元素(约500 MB)。

UPD 我对上面的样本进行了一些压力测试。 OS是x64,解决方案平台是x86。 ChunkSize是20000。

第一:

var list = new ChunkedList();
for (int i = 0; ; i++)
{
    list.Add(0.1f);
}

引发OutOfMemoryException为〜324,000,000个元素

第二:

public class RawData
{
    public string Name;
    public ChunkedList Data = new ChunkedList();
}

var list = new List<RawData>();
for (int i = 0;; i++)
{
    var raw = new RawData { Name = "Test" + i };
    for (int j = 0; j < 20 * 1000 * 1000; j++)
    {
        raw.Data.Add(0.1f);
    }
    list.Add(raw);
}

在i = 17,j~12,000,000处引发OutOfMemoryException。成功创建了17个RawData实例,每个数据点2000万个,总共约3.52亿个数据点。


14
2017-07-18 01:28



为什么只读列出兄弟? - HungPV
@HungPV,它只是展示如何组织内存的原型。无论如何,实现可编辑性肯定会很痛苦(想象项目在100万个元素的集合中删除,1个删除操作,移动元素500k操作; LinkedList对内存占用和位置不利;可能是复杂的多层次系统分块和错位图是方法)。但是,我不认为OP需要编辑集合,它似乎是简单的存储原始数据用于数值分析 - Aloraman
我尝试将List <List <float >>中的块大小减少到8192,并且我得到了大约2.3m元素的OOM,任务管理器读取了大约1.4GB的进程。 - Malik Drako
@MalikDrako,下一个候选人是其他收藏品。 pendingDataRcv有多大?此外,如果行为是可重现的,您可以在OOM之前略微暂停程序执行并查看内存分析器(例如 jetbrains.com/dotmemory关于堆的结构和什么对象保留记忆。 - Aloraman
@MalikDrako,所以瓶颈位于。我建议你扩展你的RawData,将浮动数据保存到临时文件并加载它 - 在这种情况下你可以简单地使用BinaryReader / BinaryWriter。除此之外,似乎案件已经结束:) - Aloraman