Работа с очень большими списками на 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 или ниже), но данные могут быть 10 м+. Когда длина равна 50, я попадаю на пределы памяти (OutOfMemoryException) только на 1 м данных, а когда длина равна 25, я достигал предела чуть выше 2 м точек данных. (Если мои вычисления правильные, это ровно 200 МБ, плюс размер имени плюс накладные расходы). Что я могу использовать для увеличения этого предела?

Изменить: Я попытался использовать List<List<float>> с максимальным размером внутреннего списка 1 < < 17 (131072), что несколько увеличило предел, но все же не настолько, насколько я хочу.

Edit2: Я попытался уменьшить размер куска в списке > до 8192, и я получил OOM на уровне ~ 2,3 м, а диспетчер задач с чтением ~ 1,4 ГБ для процесса. Похоже, мне нужно сократить использование памяти между источником данных и хранилищем или чаще запускать GC - я смог собрать 10 м точек данных в процессе x64 на ПК с 4 ГБ оперативной памяти, IIRC, процесс никогда не превышал 3 ГБ

Edit3: Я скомпилировал свой код только для тех частей, которые обрабатывают данные. http://pastebin.com/maYckk84

Edit4: Я посмотрел в DotMemory и обнаружил, что моя структура данных занимает ~ 1 ГБ с настройками, которые я тестировал (50ch * 3 params * 2m events = 300,000,000 float элементов), Я думаю, мне нужно будет ограничить его на x86 или выяснить, как писать на диск в этом формате, когда я получаю данные

Ответ 1

Прежде всего, на x86-системах ограничение памяти составляет 2 ГБ, а не 200 МБ. я полагаю ваша проблема намного сложнее. У вас есть агрессивная фрагментация 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 попадет в LOH только тогда, когда в коллекции будет около 16 000 кусков, что составляет более 128 миллионов элементов в коллекции (около 500 МБ).

UPD Я провел несколько стресс-тестов для примера выше. ОС - 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);
}

OutOfMemoryException возникает при я = 17, j ~ 12 000 000. 17 экземпляров RawData успешно созданы, 20 миллионов точек данных на каждый, около 352 миллионов точек данных полностью.