Конференция "Основная" » Out of Memory - непонятки
 
  • Franzy (17.11.08 14:14) [0]
    Пишу прогу, в которой приходится работать с большими массивами данных. Прога загружает файл STL (примитивный формат СAD, фактически - список треугольников в формате: список кооржинат узлов плюс нормаль), совершает некие операции последовательно с каждым треугольником, затем записывает результат операций в другой файл.

    С тестовыми (небольшими) файлами работает. С большими - в определенный момент вылетает с ошибкой Out Of Memory. Проблема в том, что, по моим расчетам, памяти должно хватать, причем с лихвой. При счете открываю Диспетчер задач, вижу по графику, что жрет 100 мегов там, где должно хватать одного мега. Т.е. имеет место memory leak, который я не могу определить.

    Объектов никаких не использую, зато использую динамические массивы (а также динамические массивы динамических массивов). Возможно, я как-то не так освобождаю память?

    Вот характерный пример используемых мною структур данных:

     Type

     TBaseContour = record
       length : longint; //кол-во узлов
       node : array of longint; //список узлов базового контура
     End;

     TBaseFace = record
       contour : array of TBaseContour; //список баз. контуров грани
       nContours : longint; //Кол-во контуров в грани
       facet : array of longint; //список номеров треугольников, образующих грань
       nfacets: longint; //кол-во тр-ков
     End;

     TBaseVolume = record
       face : array of TBaseFace; //грани, образующие объект
       nFaces : longint; //кол-во граней
     end;

     Var

     volume : array of TBaseVolume;
     nVolumes : longint; // number of volumes



    Заранее длины дин. массивов не известны, поэтому в процессе счета их длины меняются при необходимости с помощью SetLength.

    Это глобальные структуры, которые заявляются один раз (в них записываются результаты). Плюс я при анализе каждого треугольника использую дополнительные "одноразовые" массивы (тоже динамические). Чтобы освободить память после использования такого массива я пишу: [имя массива]:=nil;  

    Но складывается ощущение, что память при этом не освобождается, т.е. Windows вместо повторного использования зарезервированных ранее сегментов памяти резервирует новые...

    Может, я неправильно освобождаю память? Или проблема в структурах, включающих в себя динамические массивы?
  • tesseract © (17.11.08 14:29) [1]

    > Но складывается ощущение, что память при этом не освобождается,


    SetLength(mass,0)? Кучу может и не отпускать, см профайлером.
  • Franzy (17.11.08 14:34) [2]
    Еще один момент: "одноразовые" динамические массивы у меня заявляются как локальные переменные (внутри процедуры-обработчика). Это что-то меняет?
  • MBo © (17.11.08 14:37) [3]
    >Заранее длины дин. массивов не известны, поэтому в процессе счета их длины меняются при необходимости с помощью SetLength.

    Если длина массивов каждый раз увеличивается на единицу, то будет большой перерасход памяти. Выход - держать счетчик использованных элементов и увеличивать длину при заолнении массива на значительное число, или увеличивать вдвое. Можно и сразу задать длину с запасом.
  • Franzy (17.11.08 14:43) [4]

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


    Почему?
  • Anatoly Podgoretsky © (17.11.08 14:56) [5]
    > Franzy  (17.11.2008 14:34:02)  [2]

    Ничего не меняет, массив занимает всего 4 байта, а данные хранятся в куче.
  • sniknik © (17.11.08 14:57) [6]
    потому, что увеличение делается не "растягиванием" используемой под массив памяти на размер ещё одного элемента, а выделением нового блока большего на этот размер и копированием в него содержимого первого блока.
    память же из под первого "теряется", т.к. выделит её повторно при запрашивании следующего блока проблематично... размер этого блока будет гарантированно меньше т.к. следующий будет ещё на единицу больше...
  • han_malign © (17.11.08 14:58) [7]
    древовидные структура обычно реализуются через связные списки, особенно если подразумевается последовательная обработка узлов...
  • Anatoly Podgoretsky © (17.11.08 14:59) [8]
    > Franzy  (17.11.2008 14:43:04)  [4]

    Потому что, для перераспределения массива, старый блок использовать не получится, он меньше чем нужно и поэтому будет выделяться новый блок и содержимое будет копироваться в него и будет постоянный рост памяти, на каждом шаге 2N+1
    При больших размерах память быстро кончится. Нужна реализация наращивания памяти не по единицы, а например сразу на 25% и самостоятельно отслеживать выход за границу. Алгоритм реализован в TList
  • Sapersky (17.11.08 15:01) [9]
    Franzy   (17.11.08 14:43) [4]

    Ну есть такой недостаток у дельфийского менеджера памяти для версий < 2006. То ли чрезмерная фрагментация блоков у него начинается, то ли ещё что-то. Видимо, он больше рассчитан на использование в стиле TList, с выделением большого кол-ва маленьких кусочков без изменения размера.

    В 2006 и далее встроен FastMM, у которого, вроде, такой проблемы нет. FastMM, кстати, можно использовать и с более ранними версиями.

    Другой вариант - как уже говорили, перевыделять большими блоками. Пример:
    http://sapersky.narod.ru/files/Arrays.rar
  • Franzy (17.11.08 15:04) [10]
    Все ясно. Да, дело скорее всего в этом. Честно говоря, надеялся, что в Дельфях работа с памятью получше организована... :(
  • Franzy (17.11.08 15:09) [11]
    А если длина массива уменьшается, он как, остается на месте или тоже перезаписывается целиком в новое место?
  • Anatoly Podgoretsky © (17.11.08 15:13) [12]
    > sniknik  (17.11.2008 14:57:06)  [6]

    Растягивание тоже делается, если блок крайний.
  • Anatoly Podgoretsky © (17.11.08 15:15) [13]
    > Franzy  (17.11.2008 15:04:10)  [10]

    Диспетчер памяти всегда компромис, между скорость и ресурсами.
  • Anatoly Podgoretsky © (17.11.08 15:17) [14]
    > Franzy  (17.11.2008 15:09:11)  [11]

    Сделай тесты, наращивай массив в цикле выводи значение адреса, тоже сделай и в реальной программе.
  • Sapersky (17.11.08 15:23) [15]
    А если длина массива уменьшается, он как, остается на месте или тоже перезаписывается целиком в новое место?

    По идее должен оставаться, хотя зависит от конкретной реализации менеджера памяти.
    Однозначно перезаписывается - если RefCount > 1, т.е. на данный блок ссылаются две и более переменных-массива (было присвоение arr2 := arr1).
  • sniknik © (17.11.08 15:23) [16]
    > А если длина массива уменьшается, он как, остается на месте или тоже перезаписывается целиком в новое место?
    а сам то ты как думаешь? представь что ты разрабатываешь свой менеджер памяти
    вот тебе 4 массива в памяти, как ты увеличишь первый на единицу? желательно затратив меньше ресурсов/времени, так чтобы не было "такой проблемы"?
    1112222
    33333

    444444
    есть вообще возможность оставить его на месте? и второй вопрос, есть ли возможность оставить его на месте при уменьшении?
    имхо, это очевидно. просто чуть логики.

    > Растягивание тоже делается, если блок крайний.
    логично, но имхо, это бывает крайне редко...
  • Anatoly Podgoretsky © (17.11.08 15:37) [17]
    > sniknik  (17.11.2008 15:23:16)  [16]

    Конечно редко, поскольку трудно обеспечить, что ни будь уже захватило следующий байт вот и придется копировать.
    Я кстати предложил сделать простой тест, в цикле ситуация расширения весьма вероятна. Во всяком случае покажет как это работает.
  • Franzy (17.11.08 15:40) [18]
    По идее, конечно, массив должен на месте остаться. Но черт знает этот менеджер... Потому и спрашиваю :)
  • Franzy (17.11.08 16:03) [19]
    2Sapersky

    Скачал FastMM4, скомпилировал с ним... Библиотека творит чудеса! Больше память никуда не теряется. Спасибо!!!
  • sniknik © (17.11.08 16:54) [20]
    > Скачал FastMM4, скомпилировал с ним... Библиотека творит чудеса! Больше память никуда не теряется. Спасибо!!!
    она и без неё никуда не теряется, в прямом смысле, в переносном же она потеряна для работы т.к. ты её зафрагментировал неправильним использованием по самое не могу...
    т.е. чудо только в том, видимо, что она временами проводит "сборку мусора" объединяет неиспользуемые куски в большие блоки. (фактически "замазывает" твои ошибки, ведь неправильная работа никуда не делась... просто после кто то исправляет последствия)
    насчёт сборки мусора в FastMM4, это конечно имхо, догадки, на самом деле не знаю как она реально работает.

    но можно проверить. вот эта функция сделает примерно тоже самое (подожмёт используемую память, уберет фрагментированные блоки, и отдаст "лишнее" системе)
    SetProcessWorkingSetSize(GetCurrentProcess, dword(-1), dword(-1));
    убери FastMM4, и сам время от времени вызывай эту функцию. результат должен быть аналогичным (если я прав конечно).

    это я к чему, библиотеки это дело хорошее (хорошие библиотеки)... но и самому надо писать правильно. (типа, на Гейтца надейся а сам не плошай. :))
  • Franzy (19.11.08 11:08) [21]
    Размер pas-файла FastMM - 340кб. Вряд ли там только это :) Она полностью заменяет менеджер памяти Дельфей.
  • sniknik © (19.11.08 11:26) [22]
    никто и не говорил что "там только это", но это именно та часть которая прячет твои ошибки по работе с памятью.

    трудно что ли проверить? рем на обьявление модуля FastMM4 поставить и в цикл себе вставить указанную функцию?
    слишком частый вызов скорее всего приведет к тормозам, но проблему нехватки памяти уберет, и если уберет... ну. это повод задуматься. чего ты до сих пор кажется избегаешь.  нашёл себе "волшебный" модуль, списал свои глюки на неправильную работу менеджера дельфи и тихо радуешься, думать не надо...
  • Sapersky (19.11.08 15:50) [23]
    Фрагментация, ИМХО, возникает на уровне дельфийского менеджера памяти, а не системного, так что SetProcessWorkingSetSize не поможет. И в FastMM эта функция не используется.

    Ничего дурного в решении проблемы с использованием FastMM не вижу. Почему бы не переложить рутину на "волшебный" модуль? По сути, насколько я смог разлядеть, он автоматически делает то, что пришлось бы делать вручную - выделяет память "с запасом". Да, вручную, возможно, получилось бы эффективнее - но в большинстве случаев "овчинка выделки не стоит".

    Опять же, по вашей логике следует отказаться и от стандартного дельфийского менеджера... ведь он тоже "замазывает" кое-какие ошибки - выделение большого кол-ва маленьких блоков, например.
    Позор! Нормальный программист должен всё делать вручную! :)
  • sniknik © (19.11.08 19:21) [24]
    > Фрагментация, ИМХО, возникает на уровне дельфийского менеджера памяти, а не системного, так что SetProcessWorkingSetSize не поможет.
    истинно так, именно в дельфийском, но функция требует от программы освободить неиспользуемую память, что без дефрагментации (поджимания используемых блоков начало, и отделения неиспользуемого "конца") невозможно.
    спорить не буду, с самого начала сказал что не уверен, и надо проверить, без этого все переливание из пустого в порожнее.

    > И в FastMM эта функция не используется.
    а кто такое сказал? плюнь ему в глаза, или в зеркало. и вообше читай написаное, не выдумывай.
    было сказано "эта функция сделает примерно тоже самое", а не "там используется вот это ..."
     

    > Ничего дурного в решении проблемы с использованием FastMM не вижу.
    аналогично, но я вижу дурное в подобном
    for i:= 1 to 1000000 do begin
     SetLength(mas, i);
     ... код использующий добавку к массиву на еденицу...
    end;

    цикл это чисто для наглядности, это может быть не цикл а например таймер, событие и т.д., сам принцип приращения по одному.
    операция перераспределения памяти довольно "тяжеловесная".

    > ведь он тоже "замазывает" кое-какие ошибки - выделение большого кол-ва маленьких блоков, например.
    бред. даже не смешно.
  • Anatoly Podgoretsky © (19.11.08 19:33) [25]

    > SetProcessWorkingSetSize не поможет

    Она и не может помочь, поскольку работает с виртуальной памятью, а дефрагментация с физической.


    > Позор! Нормальный программист должен всё делать вручную!
    >  :)

    Должен уметь делать вручную!, а дальше в силу вступает правило - должен, но не обязан.
  • Sapersky (19.11.08 21:07) [26]
    аналогично, но я вижу дурное в подобном
    for i:= 1 to 1000000 do begin
    SetLength(mas, i);
    ... код использующий добавку к массиву на еденицу...
    end;


    А если там будет TList.Add? Конечно, лучше заранее выставить Capacity - но допустим, что оно неизвестно.
    А если алгоритм перевыделения наподобие TList реализован непосредственно в менеджере? Судя по комментариям в FastMM - он именно так и работает:

         {This pointer is being reallocated to a larger block and therefore it is
          logical to assume that it may be enlarged again. Since reallocations are
          expensive, there is a minimum upsize percentage to avoid unnecessary
          future move operations.}
         {Must grow with at least 100% + x bytes}
         LNewAllocSize := LOldAvailableSize * 2 + SmallBlockUpsizeAdder;

    Для "средних" блоков перевыделение делается экономнее:

           {Couldn't upsize in place. Grab a new block and move the data across:
            If we have to reallocate and move medium blocks, we grow by at
            least 25%}
           LMinimumUpsize := LOldAvailableSize + (LOldAvailableSize shr 2);

    Для "больших" тоже 25%.

    > ведь он тоже "замазывает" кое-какие ошибки - выделение большого кол-ва маленьких блоков, например.
    бред. даже не смешно.


    Ну то есть выделение большого кол-ва маленьких блоков через дельфийский менеджер будет быстрее, чем через системный, т.к. дельфийский запрашивает у системы сразу много памяти (минимизируя кол-во системных вызовов) и затем отдаёт программе по кусочкам.
    Во всяком случае, так я понял статью:
    http://www.rsdn.ru/article/Delphi/memmanager.xml
    ...хотя читал, честно говоря, "по диагонали", в каких-то деталях могу ошибаться.
  • novex (19.11.08 21:30) [27]

    > Чтобы освободить память после использования такого массива
    > я пишу: [имя массива]:=nil;


    я чето невкурю, а разве больше не надо делать free? а потом := nil?
    меня в школе учили что выделенную память всегда нужно освобождать самому и не надеятся на сборшики мусора, неужели что-то изменилось?
  • Германн © (19.11.08 21:56) [28]

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

    Да. Появились динамические массивы. :)
  • sniknik © (19.11.08 23:04) [29]
    > Судя по комментариям в FastMM - он именно так и работает:
    > ...
    ну, вот этого я как раз и не знал, думал он идет "от обратного" и наоборот собирает в кучу всю бездумно зафрагментированную программистом память. но я честно предупреждал что не знаю как он на самом деле работает...

    > Во всяком случае, так я понял статью:
    все правильно, но почему ты это назвал ошибками и "выделением большого кол-ва маленьких блоков", реально то наоборот, запрашивается один большой, который и распределяется после.
  • Anatoly Podgoretsky © (19.11.08 23:53) [30]
    > novex  (19.11.2008 21:30:27)  [27]

    Какое еще Free - это же не объект
  • Amoeba © (20.11.08 15:44) [31]

    > novex   (19.11.08 21:30) [27]
    >
    >
    > > Чтобы освободить память после использования такого массива
    > > я пишу: [имя массива]:=nil

    В случае динамических массивов (и длинных строк) компилятор самостоятельно добавляет код выполняющий освобождение выделенной памяти.
    http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1184
  • имя (01.04.09 12:05) [32]
    Удалено модератором
 
Конференция "Основная" » Out of Memory - непонятки
Есть новые Нет новых   [134451   +27][b:0][p:0.002]