Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Delphi программирование > Общие вопросы Delphi
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 25.02.2013, 13:01   #1
Freimaks
Пользователь
 
Регистрация: 23.04.2011
Сообщений: 42
По умолчанию Out of memory и правильная работа с памятью

Всем привет!
Опять взялся за свою старую идею - написать софтинку, которая будет удалять одинаковые записи из файла.
Еще на самых первых шагах меня предупреждали, что в конце концов я нарвусь на ошибку Out of memory, так вот я на нее и нарвался.
Сама ошибка возникает только на больших файлах, да и то не всегда. Даже один и тот же файл может как отработать без этой ошибки, так и с ошибкой на разных этапах обработки. Если же запускать кучу файлов, то ошибка возникает со 100% вероятностью.
Много чего почитал для борьбы с этой ошибкой, вроде даже что-то переделал, но все-равно не помогает.
Очень прошу помощи!!!

Вот код (с комментариями конечно):
Код:
procedure WorkWithCoordTimes(FileName: String);
var i, TotalCount, Count, ControlCount:integer;
BinKey: SizeBinKey; //переменная для формирования ключа
BinItem: SizeBinItem; //переменная для формирования значения
BinPointsArray: array of SizeBinPoints; //массив записей
const MAX_BUFFER_SIZE = 10000000; //В массив за один раз загоняется не более 10 млн. записей, каждая 24 байта
begin
  with TBinaryReader.Create(FileName) do
    try
      Dictionary := TDictionary<SizeBinKey, SizeBinItem>.Create;
      BaseStream.Read(BinHeader, SizeOf(BinHeader));
      TotalCount:=BinHeader.PntCnt;
      OldPointCount:=TotalCount;
      ControlCount:=Min(TotalCount,MAX_BUFFER_SIZE); //находим минимальное значение между общим количеством записей и тем ограничителем, который установили
      while TotalCount > 0 do
        begin
          Count := Min(TotalCount, ControlCount); //вычисляем требуемую длину массива
          SetLength(BinPointsArray, Count); //задаем длину массива
          BaseStream.Read(BinPointsArray[0], Count*SizeOf(SizeBinPoints)); //заполнили массив
          Dec(TotalCount, Count); //Находим разницу между общим количеством записей и уже взятым
          for i := 0 to Count-1 do //из массива заносим в Dictionary для исключения дубликатов
            begin
              BinKey.X:=BinPointsArray[i].X;
              BinKey.Y:=BinPointsArray[i].Y;
              BinKey.Z:=BinPointsArray[i].Z;
              BinKey.Time:=BinPointsArray[i].Time;
              BinItem.Code:=BinPointsArray[i].Code;
              BinItem.Echo:=BinPointsArray[i].Echo;
              BinItem.Flag:=BinPointsArray[i].Flag;
              BinItem.Mark:=BinPointsArray[i].Mark;
              BinItem.Line:=BinPointsArray[i].Line;
              BinItem.Int:=BinPointsArray[i].Int;
              Dictionary.AddOrSetValue(BinKey,BinItem);
            end;
          Form1.Memo1.Lines.Add(IntToStr(Dictionary.Count)) ;
          BinPointsArray:=nil;SetLength(BinPointsArray, 0); //после каждого раза вычищаем массив и обнуляем его длину
        end;
    finally
      Free;
    end;
end.
Размер файла, на котором я получаю ошибку - 781 Мб.
Пока для себя выяснил, что ошибка может возникать как при первом заполнении массива, так и на следующих.
Freimaks вне форума Ответить с цитированием
Старый 25.02.2013, 13:30   #2
Serge_Bliznykov
Старожил
 
Регистрация: 09.01.2008
Сообщений: 26,229
По умолчанию

Цитата:
Код:
       Count := Min(TotalCount, ControlCount); //вычисляем требуемую длину массива
          SetLength(BinPointsArray, Count); //задаем длину массива
          BaseStream.Read(BinPointsArray[0], Count*SizeOf(SizeBinPoints)); //заполнили

 .....
          BinPointsArray:=nil;
          SetLength(BinPointsArray, 0); //после каждого раза вычищаем массив и обнуляем его длину
память, выделенную под дин.массива нужно освобождать ТАК:
Код:
SetLength(BinPointsArray, 0); //освобождаем память, выделенную под массив
То, что выделено красным - УДАЛИТЕ ИЗ КОДА НАПРОЧЬ!!!!!
Serge_Bliznykov вне форума Ответить с цитированием
Старый 25.02.2013, 13:37   #3
Freimaks
Пользователь
 
Регистрация: 23.04.2011
Сообщений: 42
По умолчанию

Хм, если честно - не знал... Спасибо, за совет!!!
Но проблему это конечно не решает.
Freimaks вне форума Ответить с цитированием
Старый 25.02.2013, 13:53   #4
Serge_Bliznykov
Старожил
 
Регистрация: 09.01.2008
Сообщений: 26,229
По умолчанию

Цитата:
Но проблему это конечно не решает.
так дальше ищите утечки памяти!

например,
Цитата:
Код:
Dictionary := TDictionary<SizeBinKey, SizeBinItem>.Create;
Это вы создали объект (== выделили под него память). А где Вы освободили занимаемую им память?!

ну и максимальное число записей в 10 миллионов - впечатляет! А чего мелочитесь? Чего не 10 миллиардов? (я имею в виду, что большие объёмы данных нужно обрабатывать частями...)
Serge_Bliznykov вне форума Ответить с цитированием
Старый 25.02.2013, 13:59   #5
ReportCube
Форумчанин
 
Аватар для ReportCube
 
Регистрация: 11.03.2011
Сообщений: 426
По умолчанию

А не проще всё это решать через SQL и ADO, который под такие задачи и заточен?
ReportCube вне форума Ответить с цитированием
Старый 25.02.2013, 14:05   #6
Freimaks
Пользователь
 
Регистрация: 23.04.2011
Сообщений: 42
По умолчанию

Да 10 миллионов я поставил уже в самом конце - тесты вел от 1000. Раньше то проблема была больше в скорости чтения файла и вроде как 10 млн с одной стороны неплохо подходит для больших файлов, с другой это суммарно по объему памяти не так много. Просто читать файл в 30-40 млн по 10 тысяч медленно. А грузить весь тоже не вариант - точно попаду на ошибку.
Объект, который создается строчкой
Код:
Dictionary := TDictionary<SizeBinKey, SizeBinItem>.Create;
удаляется как только он больше не нужен, т.е. после записи результата. Удаляю его вот так:
Код:
 Dictionary.Clear;
 Dictionary.Destroy;
Я то просто как мыслил - есть там некое ограничение в 800 Мб одним куском - ну в плане массивов это я обхожу тем, что ограничиваю размер массива в 10 000 000 записей, т.е. порядка 230 Мб. Я бы не удивился если бы ошибка возникала на этапе переполнения Dictionary и при этом сама программа потребляла хотя бы эти самые 800 Мб. А вот даже сейчас запустил счет по 1 млн - считывает файл в 34 млн. записей, считывает его 12 раз и выдает ошибку, при этом потребление памяти - 472 216 Кб по Диспетчеру задач.

Попробую поискать утечку...
Freimaks вне форума Ответить с цитированием
Старый 25.02.2013, 14:12   #7
Аватар
Старожил
 
Аватар для Аватар
 
Регистрация: 17.11.2010
Сообщений: 18,922
По умолчанию

Цитата:
То, что выделено красным - УДАЛИТЕ ИЗ КОДА НАПРОЧЬ!!!!!
Серж, что-то не то. Можно так освобождать память у динамических массивов. SetLength(BinPointsArray,0) и BinPointsArray:=nil дают одинаковый результат. Может я заблуждаюсь?
Если бы архитекторы строили здания так, как программисты пишут программы, то первый залетевший дятел разрушил бы цивилизацию
Аватар вне форума Ответить с цитированием
Старый 25.02.2013, 14:18   #8
evg_m
Старожил
 
Регистрация: 20.04.2008
Сообщений: 5,526
По умолчанию

а зачем вообще этот массив?
только для того чтобы "буферизовать" чтение данных из файла(с HDD) в Dictionary?
Но он уже и так буферизован.
HDD(источник) -> Stream(свой буфер!) -> Array(наш буфер!) -> Dictionary(место назначения)

читайте из Stream по одной записи и будет вам счастье! (и код проще).
Вы все равно не читаете здесь из файла, а только переносите данные из одного места в памяти (буфер Stream) в другое (данные Dictionay) через третий фрагмент памяти (array).

Буферизация обычно вначале фиксирует размер буфера (а иногда и сами буфера).
Откажитесь от "бесконечных" setLength. Задайте размер раз и навсегда. И просто следите за тем какая часть массива заполнена. (учитывая предыдущее замечание оптимальный размер =1).
программа — запись алгоритма на языке понятном транслятору

Последний раз редактировалось evg_m; 25.02.2013 в 14:30.
evg_m вне форума Ответить с цитированием
Старый 25.02.2013, 14:22   #9
Serge_Bliznykov
Старожил
 
Регистрация: 09.01.2008
Сообщений: 26,229
По умолчанию

Цитата:
Сообщение от Аватар Посмотреть сообщение
Серж, что-то не то. Можно так освобождать память у динамических массивов. SetLength(BinPointsArray,0) и BinPointsArray:=nil дают одинаковый результат. Может я заблуждаюсь?
насчёт присвоения nil - не уверен. (спорить не буду, может быть, Вы и правы...)

Но уж, в любом случае, нужно делать что-то одно, явно не стоит указателю, которому присвоен nil уже после присвоения вызывать процедуру выделения памяти с нулём!


UPDATE
Да, проверил в Delphi, действительно, присвоение указателю динамического массива nil освобождает занимаемую массивом память! Так что, в этом данном месте исходного кода ничего не должно приводить к утечками памяти..

evg_m, согласен. +1

Последний раз редактировалось Serge_Bliznykov; 25.02.2013 в 14:36.
Serge_Bliznykov вне форума Ответить с цитированием
Старый 25.02.2013, 15:00   #10
Freimaks
Пользователь
 
Регистрация: 23.04.2011
Сообщений: 42
По умолчанию

Цитата:
Сообщение от evg_m Посмотреть сообщение
а зачем вообще этот массив?
только для того чтобы "буферизовать" чтение данных из файла(с HDD) в Dictionary?
Но он уже и так буферизован.
HDD(источник) -> Stream(свой буфер!) -> Array(наш буфер!) -> Dictionary(место назначения)

читайте из Stream по одной записи и будет вам счастье! (и код проще).
Вы все равно не читаете здесь из файла, а только переносите данные из одного места в памяти (буфер Stream) в другое (данные Dictionay) через третий фрагмент памяти (array).

Буферизация обычно вначале фиксирует размер буфера (а иногда и сами буфера).
Откажитесь от "бесконечных" setLength. Задайте размер раз и навсегда. И просто следите за тем какая часть массива заполнена. (учитывая предыдущее замечание оптимальный размер =1).
Если честно, я всегда думал, что этот мой массив и используется как основной и единственный буфер.

Правильно ли я понимаю, что Вы предлагаете выполнять операцию BaseStream.Read(BinPoints, SizeOf(SizeBinPoints)) столько раз, сколько вообще записей в файле? Или я не правильно Вас понял?

Вообще даже чтение по 10 лямов никак не влияет - я даже размер объявляю единожды - сразу максимум даю и все. Весь ступор происходит при заполнении Dictionary - просто я сейчас как наблюдаю ошибку:
читаю по миллиону, в памяти сразу отделяется кусок под файл - оперативка под 813 000 Кб (сам файл 780 Мб), начинается счет, на 7-ом миллионе выдается ошибка и в памяти остается 242 344 Кб, что как раз соответствует немногим более 7 млн. записей (примерно 7 млн. 300 тысяч) - т.е. все это именно в Dictionary и именно из-за нее выдается ошибка ибо просто чтение без каких либо операций никаких ошибок не выдает. Ну по крайней мере я так думаю и понимаю.

Последний раз редактировалось Freimaks; 25.02.2013 в 17:40.
Freimaks вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Load exe into memory and run it from memory artush1984 Win Api 10 06.02.2012 18:43
Правильная работа с формами CodeNOT PHP 5 12.04.2011 12:34
Правильная работа скрола в панели. evilgeniuz Общие вопросы .NET 0 29.09.2010 00:17
Правильная работа условия if Andruha10 PHP 1 22.08.2010 21:23
Не правильная работа for .. to Neptunium Общие вопросы Delphi 7 05.04.2010 00:32