Форум программистов, компьютерный форум, киберфорум
Наши страницы
Delphi
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.85/13: Рейтинг темы: голосов - 13, средняя оценка - 4.85
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
1

Где здесь ошибка? Всё работает, но жутко утекает память!

04.07.2012, 13:59. Просмотров 2420. Ответов 30
Метки нет (Все метки)

Доброго дня!

Есть проблема! Вроде рабочая программа, без ошибок. Строит в памяти простое двоичное дерево. Но Process explorer показывает при этом просто дикий перерасход виртуальной памяти (Virtual memory -> Virtual size)! Доходит до 1900 мб и вылетает с out of memory error. При этом реальная память (Physical memory -> Working set) выделяется нормально, пропорционально размеру дерева. И её в 10-30 раз меньше, чем виртуальной.

Ещё один прикол - то, что после уничтожения дерева и всех вспомогательных ресурсов, в систему возвращается не вся память. От 10 до 150 мб висят до самого конца. Я не умею пользоваться FastMM, но попробовал. Получил полугигабайтный лог со ссылками на все узлы дерева. Типа они не уничтожены. Но этого конечно быть не может.

Прикол номер два - при использовании FastMM расход виртуальной памяти упал в 10-30 раз. Он всё равно подозрительно большой, но хотя бы не рушит программу.

Прикол номер три. Если упростить код и оставить только построение дерева, память расходуется нормально. И полностью освобождается после уничтожения дерева. В то же время, если в реальной процедуре закомментировать строчку добавления в дерево, память расходуется только на загрузку файла, всего несколько мегабайт. Копейки. Т.е. по отдельности всё работает. Тогда где же ошибка??????

На всякий случай приведу полный код. Он конечно длинный, но вдруг кому-то сразу бросится в глаза что где не так.

Надеюсь на помощь экспертов, т.к сам думал весь вчерашний день и ничего не придумал.

Добавлено через 13 минут
Вот код дерева:

Pascal
1
2
3
4
5
6
7
8
9
10
11
12
type
 PNode=^TNode;
 TNode=record
         left,right,top:PNode;
         text:string;            // Тут хранятся слова
         cnt:integer;           // А тут - частота вхождения слова в текст
       end;
 TIntArray=array of integer;
 
var
 root,tmp,tmp2:PNode;
 TreeSize:integer;    // Число узлов
Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// создать дерево
procedure NewTree();
 begin
  new(root);
  root^.top:=nil;
  root^.left:=nil;
  root^.right:=nil;
  root^.text:='';
  root^.cnt:=0;
  TreeSize:=0;
 end;
 
// Уничтожить дерево в алфавитном порядке.
// В строке возвращается отсорированный список слов
// В массиве - частота их вхождения
function DelTree(var arr:TIntArray):string;
 var
  i:integer;
 begin
  SetLength(arr,TreeSize);   // Размер массива равен числу узлов (слов)
  result:='';  i:=0;
  tmp:=root;
  repeat
   // Идём всегда влево. Если пришли, то...
   if tmp^.left<>nil then tmp:=tmp^.left else
    begin
     tmp2:=tmp;
     // Добавляем в строку слово,
     // а в массив - частоту его вхождения 
     if tmp^.text<>'' then
      begin
       result:=result+#13#10+tmp^.text;
       arr[i]:=tmp^.cnt;
       inc(i);
      end;
     // Переподключаем свою правую свою ветвь к своему родителю [слева], если он есть.
     if tmp^.top<>nil then tmp^.top^.left:=tmp^.right;
     if tmp^.right<>nil then tmp^.right^.top:=tmp^.top;
     // Выбираем куда идти дальше. Если правая ветвь есть - направо, иначе - вверх
     if tmp^.right<>nil then tmp:=tmp^.right else tmp:=tmp^.top;
     dispose(tmp2);
     continue;
    end;
  until tmp=nil;
  delete(result,1,2); // Убрать лишние #13#10
end;
 
 
// Добавить слово в дерево и вернуть TRUE
// Если оно уже есть, увеличить его популярность CNT, а затем вернуть FALSE.
function AddWordNode(s:string; cnt:integer):boolean;
 begin
  result:=false;
  tmp:=root;
  repeat
   // Идём влево
   if tmp^.text>s then
    if tmp^.left<>nil then
     begin
      tmp:=tmp^.left;
      continue;
     end else
     begin
      new(tmp^.left);
      tmp^.left^.top:=tmp;
      tmp:=tmp^.left;
      tmp^.left:=nil;
      tmp^.right:=nil;
      tmp^.text:=s;
      tmp^.cnt:=cnt;
      result:=true;
      inc(TreeSize);
      exit;
     end;
   // Идём вправо
   if tmp^.text<s then
    if tmp^.right<>nil then
     begin
      tmp:=tmp^.right;
      continue;
     end else
     begin
      new(tmp^.right);
      tmp^.right^.top:=tmp;
      tmp:=tmp^.right;
      tmp^.left:=nil;
      tmp^.right:=nil;
      tmp^.text:=s;
      tmp^.cnt:=cnt;
      result:=true;
      inc(TreeSize);
      exit;
     end;
   // Пришли.
   if tmp^.text=s then
    begin
     inc(tmp^.cnt, cnt);
     exit;
    end;
  until false;
end;
Уничтожение узла происходит одновременно с его добавлением в выходной список. Т.е. если узел не будет уничтожен, то я его не увижу, а поскольку я его вижу, то он уничтожен... Короче, все узлы уничтожаются, это подтверждается тестами.

Добавлено через 16 минут
А вот код процедуры, которая работает с деревом. Процедура загружает файлы по одному и разбирает на отдельные слова. Слова заносятся в дерево. Выполнение этой процедуры без FastMM приводит к OutOfMemory где-то на 200 000 слове (размер дерева ~200 000).

С FastMM отрабатывает нормально, но после анализа 10ГБ текста (5 млн уникальных слов) и уничтожения дерева, в памяти остаётся несколько сотен утёкших мегабайт, которые не освобождаются до завершения программы.

Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Анализ файлов
procedure TForm1.btn_fileanalClick(Sender: TObject);
const
 sym=['а'..'я','-'];
 big=['А'..'Я'];
 yos=['ё'..'Ё'];
var
 ss:TStringList;
 s,z,enc:string;
 i,r,cnt,acnt:integer;
 buf:integer;
 t:cardinal;
begin
 stop:=false;
 NewTree();
 ss:=TStringList.Create;
 
 // Добавление старых слов из словаря, вперемешку.
 // dict - это стринглист, глобальный
 // dict_cnt - это динамический массив типа integer
 SetLength(randomizator,dict.Count);
 for i:=0 to high(randomizator) do randomizator[i]:=i;
 for i:=0 to high(randomizator) do
  begin
   r:=random(high(randomizator));
   buf:=randomizator[i];
   randomizator[i]:=randomizator[r];
   randomizator[r]:=buf;
  end;
 for i:=0 to high(randomizator) do AddWordNode(dict[randomizator[i]], dict_cnt[randomizator[i]]);
 SetLength(randomizator,0);              
                                       
 // Добавление новых слов
 cnt:=0; acnt:=0; // Счётчики уникальных и неуникальных слов
 while (memo1.Lines.Count>0) and (not stop) do    
  begin
 
   // Считываем имя файла
   s:=memo1.Lines[0];
   memo1.Lines.Delete(0);
   if not fileexists(s) then continue;
 
   // Загрузка файла и преобразование кодировки
   ss.LoadFromFile(s);     // Здесь выделяется память
   s:=ss.Text+' ';           // Здесь выделяется память
   ss.Clear;                  // Здесь виртуальная память НЕ уменьшается, уменьшается только реальная
   z:='';
 
   // Приведение в нужную кодировку (системными ф-ями)
   enc:=copy(s,1,10000); downstring(enc);
   if IsUtf8(enc) then MyUtf8ToAnsi(s)
    else if IsOem(enc) then OemToChar(@s[1],@s[1]);
 
   // Анализ файла - пробегаем его весь...
   for i:= 1 to length(s) do
    begin
     case s[i] of
      'а'..'я','-': z:=z+s[i]; // Если буква - наращиваем слово
          'А'..'Я': z:=z+char(byte(s[i])+32); // Если буква - наращиваем слово
           'ё','Ё': z:=z+'е'; // Если буква - наращиваем слово
              else begin // Иначе - записываем слово в дерево
               while (length(z)>0) and (z[1]='-') do delete(z,1,1);
               while (length(z)>0) and (z[length(z)]='-') do setlength(z,length(z)-1);
               if z<>'' then
                begin
                 if AddWordNode(z,1) then inc(cnt) else inc(acnt);
                 z:='';
                end;
              end;
     end; {of case}
    end; {for}
   application.ProcessMessages;
  end;                                                                            
 dict.SetText(pchar(DelTree(dict_cnt)));  // Процедура удаления дерева возвращает строку
                                       
 // Сохранение
 dict.SaveToFile(dictname);  // Сохраняем список слов
 dict_cntf.Size:=sizeof(integer)*(high(dict_cnt)+1); // Сохраняем частоту их вхождения
 dict_cntf.Seek(0,sofrombeginning);
 for i:=0 to high(dict_cnt) do
  dict_cntf.Write(dict_cnt[i],sizeof(integer));
 dict_cntf.SaveToFile(dictname+'.cnt');
 
 ss.Free;
 application.ProcessMessages;
 messagebeep(mb_iconasterisk);
end;
Добавлено через 20 минут
Если закомментировать строку "if AddWordNode(z,1) then..." то гигантский расход памяти исчезает. Но она всё равно утекает. За 3 минуты работы утекло 8мб виртуальной и 3 мб реальной памяти.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
04.07.2012, 13:59
Ответы с готовыми решениями:

JPEG error # 53 В Delphi 7 всё работает, а в 2010 нет. Где я туплю?
Здравствуйте! Вот кусок программы, который работает, как нужно в delphi 7. ...

Ошибка преобразования типа WORD в LPWORD (в обычном Delphi всё работает)
Такой код выдает ошибку: uses ShellApi; procedure...

В Мозиле работает, в IE не работает. Где здесь ошибка?
function loadResults(params) { ...

Подскажите где ошибка, в первом примере всё работает, но с классами отказ
import glob import sys import os import pprint if sys.platform ==...

Утекает память
Доброго времени суток! Помогите исправить утечку памяти. Пишет что утечка в 37...

30
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
04.07.2012, 14:46  [ТС] 2
Прикладываю сам проект. Вырезано всё, что не имеет отношения к проблеме. Самый минимум.
0
Вложения
Тип файла: 7z Тест.7z (168.7 Кб, 37 просмотров)
deathNC
1892 / 1005 / 123
Регистрация: 08.12.2009
Сообщений: 2,792
Записей в блоге: 2
04.07.2012, 20:27 3
В строке 57:
if tmp^.text>s then

Это образно написано, или так и есть в программе? Не понимаю, что вы ожидаете увидеть в результате выполнения этого... (вроде как недопустимого)
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
04.07.2012, 20:30  [ТС] 4
Что недопустимого? Сравнение строк. А... забыл добавить - Delphi7, не юникодная.
0
deathNC
1892 / 1005 / 123
Регистрация: 08.12.2009
Сообщений: 2,792
Записей в блоге: 2
04.07.2012, 20:35 5
Не знаю. 5 лет пишу на Delphi, ни разу не сравнивал так строки
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
04.07.2012, 20:42  [ТС] 6
until false - бесконечный цикл. Условия выхода - внутри, три штуки "exit". Одно из них выполнится в любом случае - либо алгоритм дойдёт до "листа", у которого нет больше потомков, и добавит слово, либо встретит это слово по дороге и увеличит число вхождений.

Тут, кажется, загоны менеджера памяти...
0
deathNC
1892 / 1005 / 123
Регистрация: 08.12.2009
Сообщений: 2,792
Записей в блоге: 2
04.07.2012, 21:12 7
И программа не зависает при заполнении дерева?

Добавлено через 1 минуту
Просто по мне - либо там где-то зацикливается что-то, либо ты где-то чего-то лишнего создаёшь (или не удаляешь из памяти). Менеджер памяти с ума сойти не мог

Добавлено через 16 минут
И кстати, дерево для хранения слов не обязательно делать так. Можно сделать так, чтобы каждый элемент был не словом, а буквой. И что бы из ветвей складывались по буквам слова...
Это в разы уменьшит расход памяти и увеличит скорость работы.

Не по теме:

Я бы глянул твой проект, да у меня винда не запускается. Виртуальная машина гонит :wall:

0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
04.07.2012, 21:51  [ТС] 8
Жаль, что не запускается...

Нет, ничего не зацикливается, и отрабатывает как задумано. Дерево строить правильно. С другим менеджером памяти, не родным, за пару часов этот код прошел "библиотеку пухлого" (есть в торрентах) объемом в 20 гб, выделив 5 миллионов разных слов. То есть дерево содержало 5 миллионов узлов. При этом расход памяти был ровно 48 байт на узел, плюс небольшая утечка, как я уже сейчас понял, не связанная с деревом. С родным менеджером валится уже на двухстах тысячах слов... Причем зашкаливает именно виртуальная память, обычная выделяется правильно.

Добавлено через 8 минут
Тьфу, 48 - это я спутал с тестовой процедурой, там использовались строки одинаковой длины. Ну всё равно - в разумных пределах.
0
Alex_pac
1293 / 699 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
04.07.2012, 22:07 9
может работать через TObject. Тогда хоть какието гарантии что все будет очищено.
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
04.07.2012, 22:20  [ТС] 10
TObject вместо чего - вместо record? Но тогда это будет долго. Гарантия что всё очищено - полученный на выходе список слов в алфавитном порядке. Смотрите - занесение слова в список (строка 32) сопровождается удалением узла (строка 41). Одно без другого невозможно. Если на выходе я получаю ровно TreeSize слов, значит удалено TreeSize элементов, т.е. всё дерево.

К тому же, OutOfMemory случается в процессе наполнения дерева, задолго до его очистки. Он может возникнуть, например, в строке 44: ss.LoadFromFile(s); Или в следующей. И там и там выделяются большие куски памяти.
0
Alex_pac
1293 / 699 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
04.07.2012, 22:24 11
может просто скажите скока весит у вас этот файл из которого грузится дерево?

если большие объемы данных, то надо подключать БД.
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
04.07.2012, 23:03  [ТС] 12
Свят-свят, сюда ещё БД пихать. Программа, написанная за пару дней с целью найти рифму к слову Байконур (кроме дур и конур) ... Какие тут БД! Скорость важнее.

С объёмами дело обстоит так: в окно перетаскивается много мелких (1-10 мб) текстовых файлов, общий объём которых значения не имеет, поскольку программа открывает их по-одному. Считывает весь текст в строку, после чего файл закрывает и работает дальше с этой строкой - выделяет слова и заносит в дерево. Дерево, естественно, постепенно растёт.

Со стандартным менеджером памяти уже на тысяча каком-то файле возникало OutOfMemory - программа израсходовала 1900 мб виртуальной памяти непонятно на что. Явно не на дерево, в нём на момент краха было всего 200 тыс. слов.

С менеджером FastMM после 15 тысяч файлов кол-во выделенной в процессе виртуальной памяти в районе 200 мб, что соответствует кол-ву выделенной реальной памяти.

Добавлено через 8 минут
Отсюда и непонятки - дерево вроде строится правильно и само по себе, отдельно, запрашивает ровно столько памяти, сколько нужно для хранения записи (+ выравнивание).

НО. Будучи помещённым в реальную процедуру, в которой идёт работа с файлами, со строками, с множествами, именно дерево становится причиной стремительного разбухания памяти. Если закомментировать строчку 66
Pascal
1
if AddWordNode(z,1) then inc(cnt) else inc(acnt);
и не добавлять в дерево ничего, соответственно и память не будет тратиться. Ну.. будет, но не так сильно.

И всё вышесказанное верно только для стандартного менеджера памяти.
0
Alex_pac
1293 / 699 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
04.07.2012, 23:07 13
ну что тут сказать. 100 000 тыс слов закачайте а потом внезапно сделайте ".Free" для дерева. Вот тогда и будет ясно дерево это место занимает или чтото еще.

при условии что вы уже точно знаете что дерево память не расходует.

проверьте работу "парсера слов из файлов" на холостых оборотах без добавления в дерево. и посмотрите что у вас с памятью будет.
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
04.07.2012, 23:32  [ТС] 14
Ок, сейчас.

Добавлено через 22 минуты
Если закомментировать раздел "Сохранение" (строки 76-82 в листинге), проблем с памятью нет!!!!!!!!

А если не комментировать, то вот: запустил программу, записал показатели: 35мб виртуалки, 2мб working set. Включил анализ, дождался пока наберётся 450 мб виртуалки (две минуты) и остановил. Очистил дерево - вирт. память упала до 70 мб, рабочая - до 19. Очистил словарь dict и динамический массив dict_cnt - виртуальная - 45мб, рабочая - 8мб. Всё, больше очищать нечего!

Вывод:
1) 450 мб были израсходованы на дерево. Почему так много - это и есть вопрос темы
2) Имеет место утечка - 6 мб рабочей памяти. Где - непонятно.
3) Если убрать из кода заключающий блок сохранения (строки 76-82 в листинге), перерасход отпадает, но утечка остаётся. Всё бы ничего, но этот блок выполняется ПОСЛЕ заполнения дерева и никак не должен влиять на распределение памяти! Налицо подлянка со стороны компилятора и/или штатного менеджера памяти :o
0
Alex_pac
1293 / 699 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
05.07.2012, 00:08 15
короче переделать под TObject тогда будет полный Ажур.
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
05.07.2012, 00:18  [ТС] 16
Ненене! Всякие объекты - это лишние накладные расходы, не хочу. Проще переформатировать код, так, чтобы избежать бага, если это действительно баг. Вынести секцию сохранения в отдельную процедуру.

А ещё лучше - оставить FastMM, тем более что с ним код работает в 1,85 раз быстрее. И памяти берёт ровно столько, сколько нужно. Вот только небольшие утечки после завершения процедуры разбора всё равно остаются.
0
Alex_pac
1293 / 699 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
05.07.2012, 00:23 17
не думаю что проблема в компиляторе. ИМХО проверяйте логику.

Всякие объекты - это лишние накладные расходы, не хочу
судя по расходам памяти разницы видно не будет
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
05.07.2012, 00:28  [ТС] 18
Я же говорю - весь день сидел. и ночь. с логикой. Ничего не нашёл!
Там приложении проект - можете проверить? У вас какая версия делфи? Если у вас он заработает как надо, значит это точно баг 7-й версии. Чтобы было заметно как растёт Virtual Memory, надо скормить программе где-то сотню файлов на русском, можно одинаковых. Общим весом хотя бы 100 мб. Будет видно - прямо на глазах: 55, 56, 57 мб... Если не растёт, ли растёт медленно - значит бага нет))

Под накладными расходами я имел в виду скорость работы... не память)))
0
Alex_pac
1293 / 699 / 107
Регистрация: 25.05.2011
Сообщений: 2,158
Записей в блоге: 51
05.07.2012, 00:32 19
я не могу проверить программу ибо очень смутно представляю как она работает (поиск рифмы).

может для поиска не строить дерево а регуляными выражениями все отфильтровать.
0
Postscripter
7 / 7 / 1
Регистрация: 04.07.2012
Сообщений: 160
05.07.2012, 00:39  [ТС] 20
Чтобы было заметно как растёт Virtual Memory, достаточно скормить программе где-то сотню текстовых файлов на русском, можно одинаковых. Любых. Общим весом хотя бы 100 мб. В process explorer будет видно - Virtual Memory растёт прямо на глазах: 55, 56, 57 мб, при этом Physical memory заметно отстаёт от Virtual memory, и разрыв всё увеличивается...

Добавлено через 1 минуту
Цитата Сообщение от Alex_pac Посмотреть сообщение
я не могу проверить программу ибо очень смутно представляю как она работает (поиск рифмы).

может для поиска не строить дерево а регуляными выражениями все отфильтровать.
Поиска рифмы там нет, там только построение базы слов - дерева. Всё лишнее я убрал.

Добавлено через 1 минуту
А так - да, был поиск по маске. Потому что регулярные выражения - это очень долго. Очень.
0
05.07.2012, 00:39
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
05.07.2012, 00:39

Проблема с циклом. Отдельно всё работает, а запускаешь всё вместе вылезает ошибка.
Всем привет! Отдельно всё работает, а запускаешь всё вместе вылезает ошибка....

Куда утекает память
Бесполезный код проверяет время работы функции. Память выделяется под массив и...

Почему утекает память
почему при использовании getlogin утекает память? Без нее все работает отлично,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru