У меня есть приложение, которое используется при обработке изображений, и я нахожу, что я обычно выделяю массивы в размере 4000x4000 ushort, а также случайные поплавки и тому подобное. В настоящее время среда .NET имеет тенденцию к сбою в этом приложении, по-видимому, случайным образом, почти всегда с ошибкой вне памяти. 32mb не является огромным объявлением, но если .NET фрагментирует память, то очень возможно, что такие большие непрерывные распределения не ведут себя так, как ожидалось.
Есть ли способ сказать сборщику мусора быть более агрессивным или дефрагментировать память (если это проблема)? Я понимаю, что там вызовы GC.Collect и GC.WaitForPendingFinalizers, и я посыпал их довольно либерально через свой код, но я все еще получаю ошибки. Возможно, потому, что я вызываю подпрограммы dll, которые используют собственный код, но я не уверен. Я перешел на этот код на С++ и удостоверяюсь, что любая память, которую я объявляю, я удаляю, но все же я получаю эти сбои С#, поэтому я уверен, что этого не произошло. Интересно, могут ли вызовы С++ вмешиваться в GC, заставляя его оставить память, потому что она однажды взаимодействует с родным вызовом - это возможно? Если да, могу ли я отключить эту функциональность?
РЕДАКТИРОВАТЬ: Вот какой-то очень специфический код, который приведет к сбою. Согласно this SO question, мне не нужно удалять объекты BitmapSource здесь. Вот наивная версия, в ней нет GC.Collects. Обычно он прерывается на итерации от 4 до 10 процедуры отмены. Этот код заменяет конструктор в пустом проекте WPF, так как я использую WPF. Я делаю wackiness с bitmapsource из-за ограничений, которые я объяснил в своем ответе на @dthorpe ниже, а также в требованиях, перечисленных в этом вопросе SO.
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
//Attempts to create an OOM crash
//to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
int theRows = 4000, currRows;
int theColumns = 4000, currCols;
int theMaxChange = 30;
int i;
List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
byte[] displayBuffer = null;//the buffer used as a bitmap source
BitmapSource theSource = null;
for (i = 0; i < theMaxChange; i++) {
currRows = theRows - i;
currCols = theColumns - i;
theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create(currCols, currRows,
96, 96, PixelFormats.Gray8, null, displayBuffer,
(currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
System.Console.WriteLine("Got to change " + i.ToString());
System.Threading.Thread.Sleep(100);
}
//should get here. If not, then theMaxChange is too large.
//Now, go back up the undo stack.
for (i = theMaxChange - 1; i >= 0; i--) {
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create((theColumns - i), (theRows - i),
96, 96, PixelFormats.Gray8, null, displayBuffer,
((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
System.Console.WriteLine("Got to undo change " + i.ToString());
System.Threading.Thread.Sleep(100);
}
}
}
Теперь, если я явно вызываю сборщик мусора, я должен обернуть весь код во внешнем цикле, чтобы вызвать сбой OOM. Для меня это обычно происходит вокруг x = 50 или около того:
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
//Attempts to create an OOM crash
//to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
for (int x = 0; x < 1000; x++){
int theRows = 4000, currRows;
int theColumns = 4000, currCols;
int theMaxChange = 30;
int i;
List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
byte[] displayBuffer = null;//the buffer used as a bitmap source
BitmapSource theSource = null;
for (i = 0; i < theMaxChange; i++) {
currRows = theRows - i;
currCols = theColumns - i;
theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create(currCols, currRows,
96, 96, PixelFormats.Gray8, null, displayBuffer,
(currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
}
//should get here. If not, then theMaxChange is too large.
//Now, go back up the undo stack.
for (i = theMaxChange - 1; i >= 0; i--) {
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create((theColumns - i), (theRows - i),
96, 96, PixelFormats.Gray8, null, displayBuffer,
((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
GC.WaitForPendingFinalizers();//force gc to collect, because we're in scenario 2, lots of large random changes
GC.Collect();
}
System.Console.WriteLine("Got to changelist " + x.ToString());
System.Threading.Thread.Sleep(100);
}
}
}
Если я ошибаюсь в памяти в любом сценарии, если есть что-то, что я должен заметить с профилировщиком, дайте мне знать. Это довольно простая процедура.
К сожалению, похоже, что ответ @Kevin прав - это ошибка в .NET и то, как .NET обрабатывает объекты размером более 85 КБ. Эта ситуация кажется мне чрезвычайно странной; может ли Powerpoint быть перезаписана в .NET с таким ограничением или любым другим приложением Office? 85k не кажется мне большим пространством, и я также думаю, что любая программа, использующая так называемые "большие" распределения, часто становилась нестабильной в течение нескольких дней и недель при использовании .NET.
EDIT: похоже, что Кевин прав, это ограничение .NET GC. Для тех, кто не хочет следить за всем потоком,.NET имеет четыре GC heaps: gen0, gen1, gen2 и LOH (Large Object Heap). Все, что 85k или меньше, входит в одну из первых трех кучек, в зависимости от времени создания (перемещается из gen0 в gen1 в gen2 и т.д.). Объекты размером более 85 К размещаются на LOH. LOH никогда не уплотняется, поэтому в конечном итоге распределения типа, который я делаю, в конечном итоге вызовут ошибку OOM, поскольку объекты будут разбросаны по этому пространству памяти. Мы обнаружили, что переход на .NET 4.0 немного помогает решению проблемы, задерживая исключение, но не предотвращая его. Честно говоря, это немного похоже на барьер 640k - 85k должно быть достаточно для любого пользовательского приложения (перефразировать это видео обсуждение GC в .NET). Для записи Java не демонстрирует этого поведения с его GC.