Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2004.08.01;
Скачать: [xml.tar.bz2];

Вниз

Не хватает памяти.   Найти похожие ветки 

 
opoloXAI   (2004-07-12 12:02) [0]

Добрый день.
На Celeron400 с 65МГбт свободной памяти я динамически увеличиваю размер массива следующего типа:
type
TMyMassiv = record
 v1: String;
 v2: String;
 v3: Integer;
end;{record}
var
mas: array of TMyMassiv;
Увеличиваю количество элементов массива на один и добавляю новый элемент. При достижении числа элементов массива около 4000..5000 сбодной памяти остается 0 Кбт. Дальше работа программы превращается в полные "тормоза".
Вероятнее всего я нерационально отнесся к памяти, наверное стоило воспользоваться TStream. Но я не знаю как пользоваться структурой TStream of TMyMassiv. Подскажите примерчиком кода.
Заранее спасибо.


 
Тимохов ©   (2004-07-12 12:12) [1]

не зная стреднюю длину строк v1 и v2 сказать что-то сложно.

если длина примерно 30 символов, то опиасанный вами массив будет занимать 400кб, что согласитесь копейки.


 
Тимохов ©   (2004-07-12 12:14) [2]

"полные" тормоза у вас скорее в другом месте программы


 
MBo ©   (2004-07-12 12:14) [3]

>Увеличиваю количество элементов массива на один
Вот здесь и собака порылась - стоит увеличивать размер на существенное число элементов. Или воспользоваться списками TList, а может, и TStringList еще удобнее будет.


 
Sheng ©   (2004-07-12 12:21) [4]

У меня тоже были глюки с динамическими массивами. Я решил это просто - использовал стек. Скорость сразу, как говориться, на глаз раза в 4 быстрее стала, и работает устойчиво. Вот те пример кода:

PMyMassiv = ^TMyMassiv;
TMyMassiv = record
             v1: String;
             v2: String;
             v3: Integer;
             Next: PMyMassiv;
           end;

...

MyMassivFirst,MyMassiv: PMyMassiv;

дальше пишешь процедуры добавления, поиска и очистки стека:

procedure TfrmMain.AddToStack(<твои переменные для добавления> var sMyMassiv: PSongs);
begin
 if sMyMassiv=nil then New(sMyMassiv) else
   begin
     New(sMyMassiv^.Next);
     sMyMassiv:=sMyMassiv^.Next;
   end;

 sMyMassiv^.<тут присваиваешь>
 sMyMassiv^.<тут присваиваешь>
 ...

 sMyMassiv^.Next:=nil;
end;

procedure TfrmMain.KillStack(var sMyMassiv: PMyMassiv);
var
 Temp: PMyMassiv;
begin
 while sMyMassiv<>nil do
   begin
     Temp:=sMyMassiv^.Next;
     StrDispose(sMyMassiv^.Name);
     Dispose(sMyMassiv);
     sMyMassiv:=Temp;
   end;
end;

если чего не понятно - пиши


 
Тимохов ©   (2004-07-12 12:29) [5]


>   StrDispose(sMyMassiv^.Name);

вы тут вероятно имели и виду v1 and v2?

зачем это?
при dispose записи строки-элементы записи будут очищены автоматически.


 
Тимохов ©   (2004-07-12 12:31) [6]

динамические массивы работают плохо в одном случае - если в массив добавлять много элементов вот так:
SetLength(a, length(a)+1). В этом случае каждый раз вызывается reallocmem. Лучше всего брать механизм реализованный в TList: count + capacity. Capacity просто равно текущей длинне массива. А count придется хранить отдельно. Скорость добавления возрастает многократно. По опыту добавления 100000 элементов - примерно в 100 раз.


 
Sheng ©   (2004-07-12 13:07) [7]


> Тимохов ©   (12.07.04 12:29) [5]
>
> >   StrDispose(sMyMassiv^.Name);
>
> вы тут вероятно имели и виду v1 and v2?


сорри, не всё покоцал. Это я пример из проги своей тянул. Конечно же, не надо.


 
opoloXAI   (2004-07-13 11:11) [8]

Добрый день.
Я понял, что использовать динамические массивы, в принципе, никто не советует. Но всё-же хотелось бы для себя понять (дабы в следующий раз "не наступить на те-же грабли"), почему приведённый код на Win98 съедает 60МГбт памяти за считанные секунды?

TmySearchRec = record
   Size: Integer;
   Name: TFileName;
   Path: String;
 end;
TSameForm = class(TForm)
   DirectoryLB: TdfsSystemTreeView;
   StBar: TStatusBar;
   ItemFind: TMenuItem;
private
   { Private declarations }
   mas: array of TmySearchRec;
   kolFls: Int64;
   GetPamat: Int64; {специально, чтобы подсчитатать размер занимемой памяти}
   procedure ScanDir(StartDir: string);
   procedure AddFile(SchRec: TSearchRec; Dir: String);
 public
   { Public declarations }
 end;

var
 SameForm: TSameForm;

function  myFileSize(sz: Int64): String;
function  Okruglit(x: Real; nm:Word): String;
function  Stepen(y,x:Real): Real;

implementation

{$R *.dfm}

function  myFileSize(sz: Int64): String;
var ms: Real48;
begin
    ms:=sz;
    Result:=FloatToStr(ms)+" байт";
    ms:=ms/1024;
    If ms>=1 then Result:=Okruglit(ms,3)+" Кбайт";
    ms:=ms/1024;
    If ms>=1 then Result:=Okruglit(ms,3)+" Мбайт";
    ms:=ms/1024;
    If ms>=1 then Result:=Okruglit(ms,3)+" Гбайт";
end;

function  Okruglit(x: Real; nm:Word): String;
var s: String;
   tmp: Real;
   i,p: Word;
begin
    tmp:=Round(x*Stepen(10,nm))/Stepen(10,nm);
    s:=FloatToStr(tmp);
    p:=pos(",",s);
    If p=0 then begin
       s:=s+",";
       p:=length(s);
    end;
    For i:=1+length(s)-p to nm do s:=s+"0";
    Result:=s;
end;

function  Stepen(y,x:Real): Real;
begin
    Result:=exp(x*ln(y));
end;

procedure TSameForm.ItemFindClick(Sender: TObject);
begin
    kolFls:=0;
    SetLength(mas,kolFls);
    try ScanDir(DirectoryLB.Directory);
    except MessageDLG("Ошибка обращения к диску !",mtWarning,[mbYes],0);
    end;
end;

procedure TSameForm.ScanDir(StartDir: string);
var  SearchRec: TSearchRec;
begin
    If StartDir[Length(StartDir)]<>"\" then StartDir:=StartDir+"\";
    If FindFirst(StartDir+"*.*",faAnyFile,SearchRec)=0 then begin
repeat If (SearchRec.Attr and faDirectory)<>faDirectory then AddFile(SearchRec,StartDir)
                                                               else If (SearchRec.Name<>"..")and(SearchRec.Name<>".") then ScanDir(StartDir+SearchRec.Name+"\");
until FindNext(SearchRec)<>0;
FindClose(SearchRec);
    end;
    StBar.Panels[2].Text:=" "+StartDir;
    StBar.Panels[1].Text:=" Найдено : "+IntToStr(kolFls)+" файла(ов)";
    StBar.Panels[0].Text:=" Память: "+myFileSize(GetPamat);
    Application.ProcessMessages;
end;

procedure TSameForm.AddFile(SchRec: TSearchRec; Dir: String);
begin
    kolFls:=kolFls+1;
    SetLength(mas,kolFls);
    mas[kolFls-1].Size:=SchRec.Size;
    mas[kolFls-1].Name:=AnsiUpperCase(SchRec.Name);
    mas[kolFls-1].Path:=Dir;
    GetPamat:=GetPamat+SizeOf(SchRec.Size)+SizeOf(SchRec.Name)+SizeOf(Dir);
end;

Результат работы таков: когда найдено 12065 файлов свободной физической памяти нет вообще (из 80МГбт возможных), а размер файла подкачки составляет 65МГбт. Я специально ввел переменную GetPamat, чтобы считать, сколько занимается на хранение данных массива. Получается 141,387Кбт. Так куда же девается память? Может я где-то забыл поставить ... .Free???
Заранее спасибо.


 
han_malign ©   (2004-07-13 11:27) [9]

Что такое фрагментация памяти знаешь?

Под каждую строку выделяется блок памяти, под динамический массив, каждый раз выделяется непрерывный блок памяти, а в старую "дыру" он уже не влезает - вот и получается, что при SetLength(...,Length()+1), занимается память на 2*Length()+1(по грубым прикидкам), и это нарастает в геометрической прогрессии...


 
Ega23 ©   (2004-07-13 11:33) [10]

Я бы воспользовался TList.

public
List:TList; // объявил список, создать его в OnFormCreate,
           

procedure TSameForm.AddFile(SchRec: TSearchRec; Dir: String);
var
p:PmySearchRec
begin
  New(P);
  P^.Size:=SchRec.Size;
  P^.Name:=AnsiUpperCase(SchRec.Name);
  P^.Path:=Dir;
  List.Add(P);    GetPamat:=GetPamat+SizeOf(SchRec.Size)+SizeOf(SchRec.Name)+SizeOf(Dir);
end;

Только не забудь на FormDestroy сделать Dispose всем элементам списка.


 
Тимохов ©   (2004-07-13 12:45) [11]


> han_malign ©   (13.07.04 11:27) [9]
> при SetLength(...,Length()+1), занимается память на 2*Length()+1(по
> грубым прикидкам)

да...
прикидки и правда грубые.
Вы это сами придумали?


> Что такое фрагментация памяти знаешь?

А вы знаете? Вы знаете как работает с фрагментами манагер памяти дельфи?


 
ASMiD   (2004-07-13 12:54) [12]

По моему печальному и не очень опыту работы с памятью самый быстрый и достаточно надежный способ - это хапнуть много и сразу. Ведь можно же сделать предварительные прикидки - тут уже звучали и 140к и 400к это ничто в сравнении с 65М я уж молчу про файл подкачки.


 
Тимохов ©   (2004-07-13 12:55) [13]

Автору.
Я лично не говорил, что не советую пользоваться динамическими массивами. По скорости доступа с ними никакой tlist не сравнится. А при реализации механизма count+capacity (как в tlist), то и по скорости добавления не сравнится.

ЧЕстно говоря я не вижу ошибки. Вы говорите, что вы смотрите на сильное потребление памяти в процессе работы? Тогда может быть все дело в том, что сильно глубокая рукурсия? Большая глубина каталогов?


 
Тимохов ©   (2004-07-13 12:57) [14]

Автору.
Вы неверно оцениваете потребляемую память
sizeof(string) = 4. Надо брать length(string)


 
Тимохов ©   (2004-07-13 13:06) [15]


> Тимохов ©   (13.07.04 12:57) [14]

вообще говоря вернее так: потребляемую память на строку надо оценивать так

length(s) + 4{указатель на строку}+8{служебная инфа строки}+4{служебная инфа блока менеджера памяти дельфи }+1{возможно на #0 в конце}

т.е. длина строки + 17байт, если строка не пустая.


 
Igorek ©   (2004-07-13 13:10) [16]


> Тимохов ©   (13.07.04 12:45) [11]
> > Что такое фрагментация памяти знаешь?
> А вы знаете? Вы знаете как работает с фрагментами манагер
> памяти дельфи?

А вы? :-)
Если я правильно догадываюсь, то поскольку каждому приложению выделяется виртуальное пространство, то непрерывный блок памяти в этом пространстве может совсем не быть таковым в памяти оперативной. Я прав?


 
Тимохов ©   (2004-07-13 13:14) [17]


> то непрерывный блок памяти в этом пространстве может совсем
> не быть таковым в памяти оперативной. Я прав?

не знаю.
это особенность реализации уже не менеджедра памяти дельфи, а самой ОС windows. А они это не документируют.
Вас это имхо волновать не должно.

Ваш вопрос не относится ни в коей мере к теме "Фрагментация памяти в дельфи" :р) Задайте что-нибудь более конкретное.


 
opoloXAI   (2004-07-13 13:20) [18]


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

Получается, что проблема совсем не в динамическом массиве, и проблему "пожерания памяти" мне просто не решить?


 
Тимохов ©   (2004-07-13 13:21) [19]

в димамический массивах проблемы вообще нет.

вы на вопрос не ответили...


 
opoloXAI   (2004-07-13 13:24) [20]

Если Вы о большой глубине катклогов, то я бы ответил, что она совсем не большая. Машина стоит на работе 1,2 ГБт занято на диске музыкой и рабочей требухой (не Windows\Temporary...). Вложеность не сильно большая. !!??


 
Тимохов ©   (2004-07-13 13:27) [21]


> Вложеность не сильно большая. !!??

не сильно.


 
Тимохов ©   (2004-07-13 13:29) [22]

Автору.

Вы перечитайте мои советы по поводу оченки занимаемой памяти под строки.
Может у вас реально занимается очень много.
Т.к. в своих оценках вы вообще игнорировали длинну строк, то может оказаться, что действительно занимается 65мб, т.к. например есть дикректоря с длинющими именами файлов.

посмотрите.


 
Igorek ©   (2004-07-13 13:33) [23]


> Тимохов ©   (13.07.04 13:14) [17]
> Ваш вопрос не относится ни в коей мере к теме "Фрагментация
> памяти в дельфи" :р) Задайте что-нибудь более конкретное.

Простите. Как работает менеджер памяти Дельфи в двух словах? А то я думал что его вообще нету.


 
Тимохов ©   (2004-07-13 13:36) [24]


> Дельфи в двух словах?


http://rsdn.ru/article/Delphi/memmanager.xml

только уговор - пока не переварите не делайте заявлений, ок? :)))


 
opoloXAI   (2004-07-13 13:38) [25]

К сожалению на работе Delphi не стоит, смогу проверить только вечером дома. Пока спасибо, дома посмотрю.
Тогда вопрос по "лик-безу": я описываю тип:
TmySearchRec = record
  Size: Integer;
  Name: TFileName;
  Path: String;
end;
Может стоить сделать по-другому:
1) если бы не было пупи к файлу, я бы точно съэкономил место?
2) тип String, наверное, стоит описать как String[255]?
3) тип TFileName это ведь тоже String, может его стоить описать как String[25]?


 
Тимохов ©   (2004-07-13 13:41) [26]


> opoloXAI   (13.07.04 13:38) [25]

совет. не гадайте пока.
проверьте дома сколько все-таки памяти получается.

string[255] всегда будет занимать 256 байт, тогда как string только сколько нужно + накладные расходы.
не мудрите. выполните снаяала совет в начеле ответа.


 
Igorek ©   (2004-07-13 13:43) [27]


> Тимохов ©   (13.07.04 13:36) [24]
> только уговор - пока не переварите не делайте заявлений,
> ок? :)))

железно! :-)


 
Igorek ©   (2004-07-13 13:44) [28]

Удалено модератором


 
Anatoly Podgoretsky ©   (2004-07-13 13:52) [29]

Тимохов ©   (13.07.04 13:06) [15]
+21


 
ASMiD   (2004-07-13 13:55) [30]

А я тут подумал - а если путь занимает больше 255 байт - что будет тогда? По-моему полный и непредсказуемый бардак.


 
Тимохов ©   (2004-07-13 14:26) [31]

Прокомментирую ответ 29 - поправку моего ответа 15.

Действительно для оценки занимаемой памяти непустой строкой к длинне строки надо прибавлсять 21, а не 17 байт.


 
han_malign ©   (2004-07-13 14:45) [32]

>А вы знаете? Вы знаете как работает с фрагментами манагер памяти дельфи?
- я то как раз знаю...

>Тогда может быть все дело в том, что сильно глубокая рукурсия?
- а вы знаете как работает рекурсия?
При "сильно глубокой рекурсии", будет Stack overflow. И уж стек никогда не свопируется и тормозов, сверх обычных, давать не может.

>+4{служебная инфа блока менеджера памяти дельфи }
- тогда выровнять на размер 4, и еще +4, поскольку флаг/размер повторяется на конце блока, со смещением кратным 4. Еще, тогда уж, не стоит забывать про двусвязный список информации о непрерывных блоках.

>потребляемую память на строку надо оценивать так
- если учитывать, что размер пути обычно(а для не NTFS всегда) не превышает 260(Windows.MAX_PATH), то 12000 записей это - 12000*(6+260+2*21(возьмем цифру от AP))=3696000 - так что считай не считай, а 141MБ не получится.


 
Тимохов ©   (2004-07-13 14:49) [33]


> han_malign ©   (13.07.04 14:45) [32]

ладно, не возмущайтесь :)))

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


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

Думаю его не надо брать во внимание. Т.к. свободные блоки имеют обыкновение объекдиняться и часто их не так много.

ЗЫ. Все вышесказанное про штатный манагер памяти - getmem.int.


 
Тимохов ©   (2004-07-13 14:51) [34]

Хочу еще раз дополнить информацию о том как считать сколько занимает памяти непустая строка.

Как я сейчас выяснил - в д5 и д6 разная реализация длинных строк: в д5 длинна служебного блока строки равна 12 байт, в д6 - 8.

Поэтому в д5 накладных расходов на хранение строки + 22
В д6 + 18.


 
Тимохов ©   (2004-07-13 14:55) [35]


> han_malign ©   (13.07.04 14:45) [32]

И вообще не обижайтесь.
Просто ваша формала

length(s)+1 = 2*length(s) + 1 весьма поспешна.

Дело в том, что setlength идут не подряд. Они перемижаются с работой со строками, которые тоже идут из манагера памяти. Поэтому, что там творится в манагере никому неизвестно. Может там полно огромных свободных блоков?

Да, согласен, что опыт показывает, что при последовательном увеличении памяти памяти жрестя больше чем нужно. Но согласитель, что не в 100 раз и это вряд ли может служить причиной ошибки, заявленной автором.


 
opoloXAI   (2004-07-14 16:33) [36]

Добрый день.
Может ли кто-нибудь у себя откомпилировать (я делал на Delphi6) и проверить на Win98 следующий код:

procedure TForm1.Button1Click(Sender: TObject);
type TmySearchRec = record
   Size: Integer;
   Name: TFileName;
   Path: String;
    end;
var i: Integer;
   kolFls,GetPamat: Int64;
   mas: array of TmySearchRec;
begin
    kolFls:=0;
    GetPamat:=0;
    SetLength(mas,kolFls);
    For i:=0 to 10000 do begin
        kolFls:=kolFls+1;
        SetLength(mas,kolFls);
        mas[kolFls-1].Size:=123456;
        mas[kolFls-1].Name:="proba.txt";
        mas[kolFls-1].Path:="C:\Work\Delphi";
        GetPamat:=GetPamat+SizeOf(mas[kolFls-1].Size)
                          +length(mas[kolFls-1].Name)+18
                          +length(mas[kolFls-1].Path)+18;
        Label1.Caption:=IntToStr(i);
        Label2.Caption:=IntToStr(GetPamat);
        Application.ProcessMessages;
    end;
    SetLength(mas,0);
end;

При работе программы запустить "Системеный монитор" и посмотреть "Свободная физическая память" и "Занято в файле подкачки". Посмотреть, что твориться. На моей здешней (на работе) машине по окончании работы программы свободной памяти 0 МГбт, подкачка - 60 МГбт. Может у меня на самом деле проблемы с этой машиной а не с Delphi? А как у Вас?


 
Тимохов ©   (2004-07-14 17:01) [37]

вообще говоря к концу цикла по данным диспетчера задач у меня занимает 120мб памяти.
при этом если ПОСЛЕ отработки цикла сделать minimize/maximaize окна, то по данным того же диспетчера приложение займет 2мб. Т.е. объединит и освободит лишние фрагменты.

При этом если добавлять не по 1, а выделять сразу все (или хотябы по многу, например по 1000 штук), то такого не будет.

Посмотрите в TList count+capacity - сделате так.

Готов признать, что в данном вопросе han_malign © скорее прав, чем не прав. Прошу прощения.

ЗЫ. Все-таки, чтобы немного оправдаться перед han_malign © скажу, что если внутри цикла вставить активную и независимую работу с манагером (строки, дин массивы, классы и т.д.), то такого эффекта не наблюдается. Т.е. мое отверждение про то, что манагер умеет объединять блоки и не всегда можно утвержадать про фрагментацию не лишено смысла.
Но еще раз в данном случае han_malign © прав.


 
Тимохов ©   (2004-07-14 17:25) [38]

я тут еще покапался.

если убрать строчки      
  mas[kolFls-1].Size:=123456;
  mas[kolFls-1].Name:="proba.txt";
  mas[kolFls-1].Path:="C:\Work\Delphi";

то нет никаких 120мб, только 2мб.
ясно, что эти строчки приводят к физическому копированию строки (это показал cpu). Но тогда не ясно почему же такая разница.

АВТОРУ.
К сожалению, нет времени глубже копать.
Поэтому совет однозначный:
1. Либо не пользоваться дин. массивом - использовать tlist.
2. Либо пользщоваться, но реализовать для дин. массива тот же подход, что и в TList: count + capacity (см. в исходниках tlist)


 
Pavelkq   (2004-07-15 16:13) [39]

Можно использовать TStringList для добавления, а потом сразу опредлив размер дин.массива, как TStringList.Count-1, в цикле перебросить данные. Получится 2 прохода, но все равно экономно и быстро.


 
MacroDenS ©   (2004-07-15 16:44) [40]

А на мой взгляд,
автор при объявлении:

type
TMyMassiv = record
v1: String;
v2: String;
v3: Integer;
end;{record}

использует переменные типа стринг неопределенной длины!
а такая строка может занимать и до 2Гбт.
Если у тебя строки не очень длинные ( в пределах 255 символов), то лучше задай так:

TMyMassiv = record
v1: String[255];
v2: String[255];
v3: Integer[255];
end;{record}

и тогда ты с точностью до байта сможешь узнать размер одного элемента массива и проще будет отследить расходы памяти.



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

Форум: "Основная";
Текущий архив: 2004.08.01;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.58 MB
Время: 0.034 c
14-1089704267
Max Ivanych
2004-07-13 11:37
2004.08.01
Всю ночь снился Access...


1-1090392857
Дмитрий 2004
2004-07-21 10:54
2004.08.01
как в Image можно очистить рисунок


14-1089525176
Aldor_
2004-07-11 09:52
2004.08.01
Опять заголовки


1-1089568049
TechnoDreamer
2004-07-11 21:47
2004.08.01
Как в ListBox отчертить элементы


14-1089734627
Серый вильк
2004-07-13 20:03
2004.08.01
Я рад





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский