Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2015.10.11;
Скачать: CL | DM;

Вниз

Out of memory после полумесяца работы программы.   Найти похожие ветки 

 
Дмитрий Белькевич   (2011-06-07 10:46) [0]

Утечек памяти нет (судя по эврикалог). Out of memory случается где-то на 10-15 день работы приложения. Случается при попытке выделения памяти под динамический массив:

SetLength(FBuff16, AllocSLiceSz div 2);

AllocSLiceSz равен 31768000, т.е. выделяется около 31 мб.

Использую менеджер памяти FastMM.

Вопросов, как обычно, два - кто виноват и что делать...


 
Anatoly Podgoretsky ©   (2011-06-07 11:02) [1]

Странный вопрос "кто виноват", ответ бить. На лицо фрагментация памяти.


 
clickmaker ©   (2011-06-07 11:14) [2]

> что делать

как вариант - перезапускать раз в несколько суток (планировщиком задач, к примеру)
Либо пересмотреть логику распределения памяти


 
Ega23 ©   (2011-06-07 11:50) [3]


> Случается при попытке выделения памяти под динамический
> массив:


Фрагментация.
Как вариант - либо перезапускать время от времени, либо сразу здоровый массив выделить на 100 мб и работать с ним постоянно, без реаллоков.


 
QAZ   (2011-06-07 13:02) [4]

а зачем эврика если FastMM сам все тожет определять


 
RWolf ©   (2011-06-07 13:14) [5]

это какой-то временный буфер? может, размещать его на стеке?


 
Sha ©   (2011-06-07 13:50) [6]

Можно его не удалять, чтобы потом не создавать.


 
RWolf ©   (2011-06-07 13:55) [7]

или создавать, но не из-под FastMM, а средствами ОС.


 
Дмитрий Белькевич   (2011-06-07 14:24) [8]

Приложение - довольно массивный сервер, делающий много всякого разного - раздающий веб, работающий с десятками потоков. И там далеко не один этот массив используется - что бы вот так - сделать статическим - и решить проблему. Всякого разного динамического там много.
Я тоже думаю на фрагментацию памяти. Потому как валится именно после того, как долго поработают. Пробовали несколько раз перезапускать после трех-четырех дней работы - тогда работает нормально, до какого-то времени - дней 12-15, как писал.
Но всё таки удивительно - неужели реаллоки памяти могут так сильно её фрагментировать, что 32-х мегабайтный сплошной фрагмент найти невозможно?
Еще вопрос - можно ли фастмму как-то сказать, что бы он дефрагментировал память? Можно ли что-то кроме перезапуска придумать? Предполагается, что сервер доступен постоянно (насколько железо позволяет, конечно).


 
RWolf ©   (2011-06-07 14:40) [9]

Если буферы выделять средствами операционки (т.е. HeapAlloc), можно время от времени делать HeapCompact; по идее, это должно убирать фрагментацию памяти процесса.


 
Sha ©   (2011-06-07 16:06) [10]

> Дмитрий Белькевич   (07.06.11 14:24) [8]
> И там далеко не один этот массив используется - что бы вот так -
> сделать статическим - и решить проблему.
> Всякого разного динамического там много.

Можно организовать повторное использование только больших кусков.


 
Дмитрий Белькевич   (2011-06-07 16:10) [11]

Попробовал тестово повыделять память:


procedure TForm19.Button1Click(Sender: TObject);
var
t: array of Byte;
begin
i := 0;
repeat
 if Random(2) = 1 then
  SetLength(f[Random(999)], random(1000000))
 else
  SetLength(f[Random(999)], 0);
 Label1.Caption := IntToStr(i);
 Inc(i);
 SetLength(t, 32000000);
 SetLength(t, 0);
 Application.ProcessMessages;
until False;
end;


второй час - полет нормальный. Пока не представляю, как глюк повторить - не 10 же дней ждать :)


 
sniknik ©   (2011-06-07 16:19) [12]

может зря на память "гонишь", может что-то в логике? (вряд ли эврикалог/... отследит), ну к примеру потоки не завершаются (не падают/выходят без очистки/..., а именно просто впустую крутятся, аля зомби поток...)


 
_Юрий   (2011-06-07 20:25) [13]

У меня ровно тоже самое:
COM - сервер, утечек нет, судя по показаниям FastMM
А вылет с OutOfMemory где-то раз-два в месяц.


 
_Юрий   (2011-06-07 20:28) [14]

Но у меня непрерывные большие куски не выделяются. Никаких массивов, только списки объектов


 
Sha ©   (2011-06-07 20:29) [15]

Вот такая процедура вылетает у меня (D7 без FastMM) на 1543 проходе цикла,
т.е. когда реально используется всего 2.5М памяти.

procedure TForm1.Button1Click(Sender: TObject);
var
 s: string;
 a: array of string;
 i: integer;
begin;
 i:=0;
 try
   while true do begin;
     inc(i);
     SetLength(s,(1000+i)*1000);
     SetLength(a,i);
     SetLength(a[i-1],1);
     end;
 except
   Memo1.Lines.Add(IntToStr(i));
   end;
 end;


 
Дмитрий Белькевич   (2011-06-08 00:02) [16]


> может зря на память "гонишь", может что-то в логике? (вряд
> ли эврикалог/... отследит), ну к примеру потоки не завершаются
> (не падают/выходят без очистки/..., а именно просто впустую
> крутятся, аля зомби поток...)


А я вижу картину падения в эврикалоге. Увы - ничего крамольного.

Полный кусок кода:


    try
     SetLength(FBuff16, AllocSLiceSz div 2);
    except
     on E: Exception do
     begin
      StandardEurekaError(ansistring(Format("Alloc memory raise an exception ! %s. Alloc Size: %d.", [E.Message, AllocSLiceSz])));
      Halt(0);
     end;
    end;


Так вот - эврика успешно создаёт лог, ничего особенного в нём нет.


 
Дмитрий Белькевич   (2011-06-08 00:10) [17]

Эврика список всех потоков приложения показывает, если что. Всё в норме - штук 15 крутится всякого-разного, которое и так почти всегда работает.


> Вот такая процедура вылетает у меня (D7 без FastMM) на 1543
> проходе цикла,


Подтверждаю. Реально ли это исправить, кроме как рестартом?


 
Sha ©   (2011-06-08 01:18) [18]

> Реально ли это исправить, кроме как рестартом?

Чтобы было совсем нереально, наверно,  постараться надо.
Конкретный рецепт, конечно, от программы зависит, но основные идеи:
для больших кусков запрашивать памяти больше, чем надо, и повторно использовать,
для маленьких - запрашивать память в правильной последовательности, не допускать фрагментации,
возможно, удобнее будет от строк перейти к буферам.


 
Sha ©   (2011-06-08 01:24) [19]

В [18] имеется в виду переход к пулу буферов фиксированного размера,
которые освобождаются только по окончании работы программы.


 
sniknik ©   (2011-06-08 07:58) [20]

> Реально ли это исправить, кроме как рестартом?
всегда обнулял в финале, если выделял, после использования, проблем не было (по аналогии раз выделил освободи)

тут, типа -
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
a: array of string;
i: integer;
begin;
i:=0;
try
  while true do begin;
    inc(i);
    SetLength(s,(1000+i)*1000);
    SetLength(a,i);
    SetLength(a[i-1],1);
    SetLength(s, 0);
    SetLength(a, 0);
    end;
except
  Memo1.Lines.Add(IntToStr(i));
  end;
end;


 
Дмитрий Белькевич   (2011-06-08 08:38) [21]


> Конкретный рецепт, конечно, от программы зависит, но основные
> идеи:для больших кусков запрашивать памяти больше, чем надо,
>  и повторно использовать,для маленьких - запрашивать память
> в правильной последовательности, не допускать фрагментации,
> возможно, удобнее будет от строк перейти к буферам.


Всего этого придерживаюсь. Когда массивам записей SetLength делаю - выделяю по сто или по 1000 элементов за раз, позже обрезаю до нужного размера.

Но вот - всё равно вылазит Out of memory.

Интересна именно дефрагментация памяти. В FastMM готового или не нашел или плохо искал. Не представляю даже, что делать, кроме рестарта. Рыть менеджер памяти самому - как-то не хочется...


 
Sha ©   (2011-06-08 08:58) [22]

> sniknik ©   (08.06.11 07:58) [20]

Мой пример просто показывает откуда берется дефрагментация.
Простого рецепта, типа "освободи, когда станет ненужным" не существует.
Представь себе, что в моем примере массив а нужен на протяжении всего времени работы. Он занимает не более 4К всего.

procedure TForm1.Button1Click(Sender: TObject);
var
 s: string;
 a: array of string;
 i: integer;
begin;
 i:=0;
 try
   while true do begin;
     inc(i);
     SetLength(s,(1000+i)*1000);
     SetLength(a,i);
     SetLength(a[i-1],1);
     //тут работаем
     SetLength(s, 0);
     end;
 except
   Memo1.Lines.Add(IntToStr(i));
   end;
 end;

Теперь ломаемся на 1563 проходе цикла.


 
Sha ©   (2011-06-08 08:59) [23]

... откуда берется фрагментация.


 
Anatoly Podgoretsky ©   (2011-06-08 09:00) [24]


> Всего этого придерживаюсь. Когда массивам записей SetLength
> делаю - выделяю по сто или по 1000 элементов за раз, позже
> обрезаю до нужного размера.
>
> Но вот - всё равно вылазит Out of memory.

Соотвественно ты уже не сможешь выделить тот же блок на 1000 элементов, а только новый, вот так и получает фрагментация и следствие


 
sniknik ©   (2011-06-08 09:42) [25]

> Простого рецепта, типа "освободи, когда станет ненужным" не существует.
а у меня это не рецепт, это правило (еще с паскаля). которое судя по всему сильно снижает вероятность подобных эксцессов.

> позже обрезаю до нужного размера.
зачем? тут ИМХО тоже кроется возможность фрагментации, ну типа выделили 1000 после еще байт 10 в другом массиве, у после обрезали первый до 20 байт... куда денутся оставшиеся 980?
и пусть даже следом/в другом цикле  после наслоение следующих выделений будет очистка массивов 10 и 20 то будет ли перераспределение на "потерянные"  980???


 
KSergey ©   (2011-06-08 13:43) [26]

У меня есть дурацкий (не подходящий для этого случая, увы) рецепт: переходить на .NET - тама дефрагментация .NET-ядром делается периодически, волшебно или на ручной тяге.
Ну или подобным образом переписывать свою прогу.
Вот только это нереально, увы.

PS
У нас сервер всего на работу в течение дня рассчитан, и то частенько не доживает :( Бороться трудно


 
Sha ©   (2011-06-08 14:20) [27]

> выделяю по сто или по 1000 элементов за раз, позже обрезаю до нужного размера.

Так тоже не всегда хорошо делать, вот пример (облом на 2078 проходе).

procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
a: array of string;
i, count: integer;
begin;
i:=0;
count:=4000;
try
  SetLength(a, 2*count);
  while i<count do begin;
    inc(i);

    //выделяем
    SetLength(s, 1000*1000);
    SetLength(a[i*2-2], 1);

    //работаем
    SetLength(s, 1);
    a[i*2-1]:=s;

    //освобождаем
    SetLength(s, 0);
    end;
  SetLength(a, 0);
except
  Memo1.Lines.Add(IntToStr(i));
  end;
end;


 
Loginov Dmitry ©   (2011-06-08 16:18) [28]


> Вот такая процедура вылетает у меня (D7 без FastMM) на 1543
> проходе цикла,


D2010. 2ГБ ОЗУ. Ошибка возникает на 734085 итерации. При этом под строку "s" выделено памяти 735085000 байт. Что волшебного в менеджере памяти у D2010?


 
sniknik ©   (2011-06-08 17:27) [29]

> Что волшебного в менеджере памяти у D2010?
а вроде что-то про "сборщик мусора" с какой то версии дельфи говорили... ?
вот оно волшебство то.


 
RWolf ©   (2011-06-08 17:50) [30]

у FastMM или менеджера D7 есть вызовы для определения максимального размера непрерывной области памяти, которая может быть выделена?
неплохо было бы мониторить это число при отладке, чтобы избежать сабжа.


 
Sha ©   (2011-06-08 18:05) [31]

> Loginov Dmitry ©   (08.06.11 16:18) [28]
> Что волшебного в менеджере памяти у D2010?

Не перемежает разнокалиберные запросы.


 
Loginov Dmitry ©   (2011-06-08 21:12) [32]


> При этом под строку "s" выделено памяти 735085000 байт.


Кстати обманул, указал количество символов в строке, а не размер выделенной под нее памяти. В случае D2010 число нужно умножать на 2, поэтому получим ~ 1,5 ГБ.


 
han_malign   (2011-06-09 10:16) [33]


> Простого рецепта, типа "освободи, когда станет ненужным" не существует.

- простой рецепт - как можно меньше вложенных динамических объектов...

В большинстве случаев, вложенные String прекрасно заменяются на String[адекватного размера], а вложенные классы на статически размещенные структуры/объекты...


 
Дмитрий Белькевич   (2011-06-09 15:43) [34]

Позже вернусь к вопросу, попробую полную отладку в fastmm включить + поиск границ и иже. Возможно всё таки это не фрагментация, хотя я уверен процентов на 80.


 
dred2k   (2011-06-15 20:55) [35]

Советую настроить мониторинг приложения в мониторе ресурсов (системном). Промониторить все пулы памяти - выгружаемый, невыгружаемый и т.д. С записью в файл, потом - анализ. Было дело, с помощью этого выловил регулярные падения сервака по вине, как оказалось, стороннего драйвера (юзал ком-порты через шарилку - SAPS).
Тут может быть _все что угодно_. При современном уровне порой очень не очевидной интеграции. Кстати, а DLL используются в алгоритме ?


 
dred2k   (2011-06-15 20:56) [36]

В чем у меня была сложность - падала NT-я. :)


 
dred2k   (2011-06-15 21:15) [37]

На Delphi2007 тоже "волшебный" менеджер (я на тему [28]).
864272 (EOutOfMemory) = [865271000 - длина строки s]

Win7x64, 4 гига


 
dred2k   (2011-06-15 21:34) [38]

Кстати, рекомендую тулзу Process Explorer из SysInternals, он показал в свойствах процесса Peek Private Bytes как раз равным размеру строки s - 800 с чем-то метров. При этом Virtual Size был около 65 метров.
Может подумать об использовании функций класса Virtualxxx ?
Быстрые гипотезы, сорри.


 
dred2k   (2011-06-15 23:36) [39]

Решил поэкспериментировать со строками. Как при операциях меняется указатель на первый элемент ?
Ниже - пример, для внутренней переменной. Если перенести ее в глобальную область - будет интереснее...

procedure TForm1.Button2Click(Sender: TObject);
var tmpStr : String;
begin
 // Посмотрим, как изменяются указатели на первый символ
 // одной и той же строки при некоторых операциях с последней.
 with Memo1.Lines do
 begin
   Clear;
   // Вариант 1
   Add("Вариант 1");
   tmpStr := "1234";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := tmpStr + "1234567";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := "1234567890";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   Add("------------------------------------");
   // Вариант 2
   Add("Вариант 2");
   tmpStr := "1234";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := tmpStr + "12345678"; // <---- !!!!!!!!!!!!!!!!!!!!!
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := "1234567890";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   Add("------------------------------------");
   // Вариант 3
   Add("Вариант 3");
   tmpStr := "1234";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := tmpStr + "1234567";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := "12345678901"; // <---- !!!!!!!!!!!!!!!!!!!!!
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   Add("------------------------------------");
   // Вариант 4
   Add("Вариант 4");
   tmpStr := "1234";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := tmpStr + "1234567";
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   tmpStr := "123456789012"; // <---- !!!!!!!!!!!!!!!!!!!!
   Add(IntToStr(Integer(@tmpStr[1])) + " ["" + tmpStr + ""]");
   Add("------------------------------------");
 end;
end;


 
Дмитрий Белькевич   (2011-06-15 23:57) [40]


> Тут может быть _все что угодно_.


Вообще, замечено, что, вероятно, проблема в обработке веба. Сервер, среди прочего, имеет веб интерфейс к себе и генерирует (без сторонних приложений) некоторое количество - штук 30-40 разных страниц. Так вот - у нас на основном "тестовом полигоне" сервер нормально работает месяцами, но мы у себя веб не используем. У одних из пользователей, по соглашению - бэта-тестеров - сервер используется полностью, с вебом в том числе. Это, правда, слабое утешение в поиске проблемы. Опять же - возможно, что именно веб дает сильную фрагментацию памяти. Мы пока что переползли на обновленный фаст мм, попробуем еще с ним поработать. Дальше буду пробовать фуллдебаг мод. Буду пробовать снимать карту памяти (вроде бы фаст мм позволяет) после out of memory (если удастся). Попробую еще какую-то информацию вытянуть из фастмм, если не удастся встроенными средствами - попробую фастмм дописать.



Страницы: 1 2 вся ветка

Текущий архив: 2015.10.11;
Скачать: CL | DM;

Наверх




Память: 0.58 MB
Время: 0.006 c
2-1400830587
SkyWalker
2014-05-23 11:36
2015.10.11
Помогите


15-1424182937
Torry
2015-02-17 17:22
2015.10.11
Продажа torry.net


15-1423869487
Ламот
2015-02-14 02:18
2015.10.11
Робот или человек? Как "правильно" организовать проверку?


3-1305347200
Павел
2011-05-14 08:26
2015.10.11
Выборка по датам


1-1307429189
Дмитрий Белькевич
2011-06-07 10:46
2015.10.11
Out of memory после полумесяца работы программы.