Массивы, разделяющие память в.NET4.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
Я думаю о трюке, как победитель ответ на этот вопрос. Это будет возможно? Проблема в том, что длина и количество массивов сегментов известны только во время выполнения.
2 ответа
Предполагая, что вы уверены, что вашего OutOfMemoryException можно избежать, и ваш подход к тому, чтобы все это было в памяти, не является реальной проблемой (GC довольно хорошо предотвращает это, если память доступна) ...
- Вот ваша первая проблема. Я не уверен, что CLR поддерживает какой-либо один объект размером более 2 ГБ.
- Решающее Правило -
gcAllowVeryLargeObjects
измените это на 64-битных системах - попробуйте это, прежде чем переходить на собственное решение.
- Решающее Правило -
- Во-вторых, вы говорите о том, что "некоторые хранятся, некоторые - GC'd". т.е. вы хотите иметь возможность перераспределять элементы вашего массива, как только вы закончите с "дочерним массивом".
- В-третьих, я предполагаю, что
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'd.
Это возможно, оборачивая массив и отслеживая, какие разделы используются для каких дочерних массивов. По сути, вы говорите о выделении огромного куска памяти, а затем о повторном использовании его частей без нагрузки на 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
}
}
Предупреждение Приведенный выше код эффективно вводит уязвимости переполнения буфера, если, например, вы не проверяете запрошенный childIndex
против length
для дочернего массива в таких методах, как SetChildValue
, Вы должны понять это и предотвратить это, прежде чем пытаться сделать это в производстве, особенно если комбинировать эти подходы с использованием unsafe
,
Теперь это можно расширить, чтобы вернуть индекс psuedo public PointF this[int index]
методы для дочерних массивов, счетчики для дочерних массивов и т. д., но, как я уже сказал, это становится сложным, и вам нужно решить, действительно ли это решит вашу проблему. Большая часть вашего времени будет потрачена на повторное использование (первое), дефрагментацию (второе), расширение (третье) throw OutOfMemory
(последняя) логика.
Этот подход также имеет то преимущество, что вы можете выделить много подмассивов по 2 ГБ и использовать их как один массив, если мой комментарий о пределе для объекта в 2 ГБ верен.
Это предполагает, что вы не хотите идти вниз unsafe
Маршрутизация и использование указателей, но эффект тот же, вы просто создаете класс-оболочку для управления дочерними массивами в фиксированном блоке памяти.
Другой подход заключается в использовании подхода хэшсет / словарь. Выделите весь (массивный массив 2 ГБ) и разбейте его на куски (скажем, 100 элементов массива). Затем дочернему массиву будет выделено несколько чанков, а в его последнем чанке будет потрачено некоторое пространство. Это повлечет за собой влияние некоторого потраченного впустую пространства в целом (в зависимости от ваших средних прогнозов "длина дочернего элемента и длина фрагмента"), но будет иметь преимущество в том, что вы сможете увеличивать и уменьшать размер дочерних массивов, а также удалять и вставлять дочерние массивы с меньшим влиянием на вашей фрагментации.
Примечательные ссылки:
- Большие массивы в 64-битных.NET 4:
gcAllowVeryLargeObjects
MemoryFailPoint
- позволяет "запрашивать" память и проверять наличие исключений вместо сбояOutOfMemoryException
после факта- Большие массивы и фрагментация LOH. Что такое принятая конвенция?
- Предел процесса 3 ГБ для 32-битной системы, см.: 3_GB_barrier, Рекомендации по отказу сервера / 3 ГБ и AWE/PAE
- уязвимость переполнения буфера, и почему вы можете получить это в C#
Другие примеры доступа к массивам как к другому типу массива или структуры. Их реализация может помочь вам разработать собственное решение
- Класс BitArray
- Структура BitVector32
- NGenerics - умное использование понятий типа массива в некоторых членах этой библиотеки, особенно в общих структурах, таких как ObjectMatrix и Bag
- C# массив объектов, очень большой, ищет лучший способ
Оптимизация массива
- Эрик Гу - Эффективность итерации по массивам? - обратите внимание на возраст этого, но подход к поиску оптимизации JIT по-прежнему актуален даже в.NET 4.0 (см., например, Устранение проверки границ массива в CLR?)
- Дейв Детлефс - устранение проверки границ массива в CLR
- Предупреждение - pdf: проверка неявных границ массива на 64-битных архитектурах
- LinkedList - позволяет вам ссылаться на несколько разных массивов в последовательности (связывая куски в подходе с блоками)
Параллельные массивы и использование unsafe
- Параллельное матричное умножение с параллельной библиотекой задач (TPL), в частности
UnsafeSingle
- квадратный или зубчатый массив, представленный одним массивом, является тем же классом проблем, которые вы пытаетесь решить. - уязвимость переполнения буфера, и почему вы можете получить это в C# (да, я уже упоминал об этом три раза, это важно)
Ваша лучшая ставка здесь, вероятно, использовать несколько ArraySegment<PointF>
все на одном 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
код. Конечно, в зависимости от сценария, обработка огромного массива как неуправляемой памяти может (в некоторых сценариях) все еще заманчиво.