Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Резкое увеличение размера динамического массива [D6, WinXP]


Страницы:   1 2 3

vint45 ©   (2007-10-11 16:25:00) 

Здраствуйте!

Есть динамический массив, в который помещаются записи (record) с описанием файлов при сканировании диска. Записи такого вида:
 TFile = record
  Folder: string;
  FullName: string;
  Size: int64;
 end;


При сканировании всего диска создается довольно большой массив, около 60000 элементов. При этом программа вылетает с ошибкой "Out of memory".
Диспетчер задач показывает, что программа съела около 250 метров. Хотя если подсчитать сколько должен занимать массив (подсчетом длины строк помещаемых в массив + указатели), то получится примерно метров 5.
Замена типа string (AnsiString) на PChar для FullName решает проблемму (диспетчер показывает, что приложение съедает мах 3 метра). Но вопрос остается, почему при работе с AnsiString приложение выделяет так непропорционально память.
Может так диспетчер памяти выделяет блоки с резервироваем пространства, или может я все-таки что-то не так делаю (тогда могу привести весь код)?


vpbar   (2007-10-11 20:08:00) [21]

нда, код жуткий

вот
TFile = Class
 Folder: string;
 FullName: string;
 Size: int64;
end;

 TProcForScanPath = procedure(Path, InitPath: string; sr: TSearchRec) of object;
 TForm1 = class(TForm)
   Button1: TButton;
   Button2: TButton;
   Label1: TLabel;
   Label2: TLabel;
   procedure Button1Click(Sender: TObject);
 private
   { Private declarations }
 public
 fDoRecursPath: boolean;
  arrFiles: Tlist;//array of TFile;
  procedure AddFilesToArray(Path, InitPath: String; sr: TSearchRec);
  procedure ScanPath(Path, InitPath: string;Func: TProcForScanPath); // recurce
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}
var len, len2: integer;

{ TForm1 }

procedure TForm1.AddFilesToArray(Path, InitPath: String; sr: TSearchRec);
var eFile:TFile;
begin
if not (((sr.Attr and SysUtils.faDirectory)=SysUtils.faDirectory) or ((sr.Attr and SysUtils.faVolumeID)=SysUtils.faVolumeID)) then
begin
eFile:=TFile.Create;
//SetLength(arrFiles, Length(arrFiles)+1);
eFile.Folder:=InitPath;
eFile.FullName:=Path+sr.Name;
len:=len+Length(Path+sr.Name);
Label1.Caption:=inttostr(len);
Application.ProcessMessages;
eFile.Size:=sr.Size;
arrFiles.Add(eFile);
end;
end;

procedure TForm1.ScanPath(Path, InitPath: string; Func: TProcForScanPath );
var sr: TSearchRec;
  arrFold: TStringList;
  i: integer;
begin
len2:=len2+1;
Label2.Caption:=inttostr(len2);
Application.ProcessMessages;
arrFold:=TStringList.Create;

if FindFirst(Path+'*', sysutils.faAnyFile, sr) = 0 then begin
repeat
 if (sr.Name<>'.') and (sr.Name<>'..') then begin

  if (((sr.Attr and SysUtils.faDirectory)=SysUtils.faDirectory) or
     ((sr.Attr and SysUtils.faVolumeID)=SysUtils.faVolumeID)) then begin
           arrFold.Add(Path+sr.Name+'\');
  end
  else Func(Path, InitPath, sr);

 end;
until FindNext(sr)<>0;
end;
FindClose(sr);
for i:=0 to arrFold.Count-1 do ScanPath(arrFold[i],InitPath,Func);
arrFold.Free;
len2:=len2-1;
Label2.Caption:=inttostr(len2);
Application.ProcessMessages;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
//fDoRecursPath:=True;
arrFiles:=TList.Create; // создавать не здесь
len2:=0;
len:=0;
ScanPath('E:\BPK\Comp_delphi\', 'E:\BPK\Comp_delphi', AddFilesToArray);
end;

работает на порядок быстрее и памяти не жрет


Суслик   (2007-10-11 20:05:00) [22]


>  [19] Sapersky   (11.10.07 19:59)
> Всё-таки виноват оказался SetLength(arrFold, Length(arrFold)+1).
> Теоретически оно не должно приводить к фатальным последствиям,
> но в данном случае приводит - видимо, из-за фрагментации.
> Если не хочется писать перевыделение блоками для каждого
> типа массивов - см. сюда:
> http://sapersky.narod.ru/files/Arrays.rar


это особенность манагера памяти в д6.
в д2007 проблемы нет, ибо манагер памяти иной.


Sapersky   (2007-10-11 20:02:00) [23]

Перепутал - не arrFold, а arrFiles, но тоже SetLength.


Sapersky   (2007-10-11 19:59:00) [24]

Всё-таки виноват оказался SetLength(arrFold, Length(arrFold)+1).
Теоретически оно не должно приводить к фатальным последствиям, но в данном случае приводит - видимо, из-за фрагментации.
Если не хочется писать перевыделение блоками для каждого типа массивов - см. сюда:
http://sapersky.narod.ru/files/Arrays.rar


vint45   (2007-10-11 17:46:00) [25]


> Суслик ©   (11.10.07 17:34) [17]

объем данных легко подсчитать через вычисление переменных len, len2. В данном алгоритме len считает общую длину строк FullName (чтобы ее посчитать, надо заккоментировать строку присвоения для FullName, иначе будет вылетать с ошибкой переполнения памяти). Len2 считает текущий уровень вложенности процедуры ScanPath, чем больше уровень тем больше локальных переменных хранится в стеке. У меня он достигал где-то 20. Тоже не критично, потому как массив arrFold хранит папки только одного уровня и не может быть большим ~ 10-20Кб * 20 = мах 400 Кб.


Суслик   (2007-10-11 17:34:00) [26]

все же - проверь объем данных. точно 60000 тыс. записей?
суммарная длина строк?

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


vpbar   (2007-10-11 17:32:00) [27]

хм. Посмотрю дома. Хотя мне вот это SetLength(arrFold, Length(arrFold)+1); не нравится. Лучше со списком строк сделать.
ЗЫ. Кстатит есть же реализация поиска могу дать, или тебе свое нужно


vint45   (2007-10-11 17:28:00) [28]

да, стоит по-умолчанию $H+ AnsiString.


vpbar   (2007-10-11 17:23:00) [29]

>>vint45   (11.10.07 17:19) [13]
эээ. Так в опцией Huge strings и директивой {$H+} у тебя все в порядке??


vint45   (2007-10-11 17:19:00) [30]


> vpbar ©   (11.10.07 17:15) [11]


ну с ShortString точно будет неэффективное расходование памяти, да и строки путей могут быть длинее, чем 255 символов. Предпочитаю с ними не работать.


vpbar   (2007-10-11 17:17:00) [31]

Т.е. если в опциях не стоит Huge strings то String будет занимать не 4 байта и 256


vpbar   (2007-10-11 17:15:00) [32]

может это

"creates a variable S that holds a string. In the default {$H+} state, the compiler interprets string (when it appears without a bracketed number after it) as AnsiString. Use the {$H-} directive to turn string into ShortString."


vint45   (2007-10-11 17:12:00) [33]

Только в данной редакции замена FullName: String на PChar не помогает, память все так же лавинообразно увеличивается до 250 метров и потом программа вылетает, хотя в основной программе такой же код увеличивает использование памяти максимум на 2 метра.


vint45   (2007-10-11 16:59:00) [34]

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

unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls;

type
 TFile = record
  Folder: string;
  FullName: string;
  Size: int64;
 end;

 TProcForScanPath = procedure(Path, InitPath: string; sr: TSearchRec) of object;

 TfmMain = class(TForm)
   Button1: TButton;
   Label1: TLabel;
   Label2: TLabel;
   procedure Button1Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
   fDoRecursPath: boolean;
   arrFiles: array of TFile;
   procedure AddFilesToArray(Path, InitPath: String; sr: TSearchRec);
   procedure ScanPath(Path, InitPath: string; Func: TProcForScanPath); // recurce
 end;

var
 fmMain: TfmMain;

implementation

{$R *.dfm}

var len, len2: integer;

procedure TfmMain.ScanPath(Path, InitPath: string; Func: TProcForScanPath); // recurce
var sr: TSearchRec;
   arrFold: array of String;
   i: integer;
begin
len2:=len2+1;
Label2.Caption:=inttostr(len2);
Application.ProcessMessages;
// ïåðåä âûçîâîì ïðîöåäóðû íåîáõîäèìî óñòàíàâëèâàòü ïåðåìåííóþ fDoRecursPath â True
if FindFirst(Path+'*', sysutils.faAnyFile, sr) = 0 then begin
 repeat
  if (sr.Name'.') and (sr.Name'..') then begin

   if (((sr.Attr and SysUtils.faDirectory)=SysUtils.faDirectory) or ((sr.Attr and SysUtils.faVolumeID)=SysUtils.faVolumeID)) then begin
    SetLength(arrFold, Length(arrFold)+1);
    arrFold[High(arrFold)]:=Path+sr.Name+'\';
   end
   else Func(Path, InitPath, sr);

   if not fDoRecursPath then begin
    FindClose(sr);
    Finalize(arrFold);
    exit;
   end;
  end;
 until FindNext(sr)0;
end;
FindClose(sr);
for i:=0 to High(arrFold) do ScanPath(arrFold[i],InitPath,Func);
Finalize(arrFold);
len2:=len2-1;
Label2.Caption:=inttostr(len2);
Application.ProcessMessages;
end;

procedure TfmMain.AddFilesToArray(Path, InitPath: String; sr: TSearchRec);
begin
if not (((sr.Attr and SysUtils.faDirectory)=SysUtils.faDirectory) or ((sr.Attr and SysUtils.faVolumeID)=SysUtils.faVolumeID)) then begin
 SetLength(arrFiles, Length(arrFiles)+1);
 arrFiles[High(arrFiles)].Folder:=InitPath;
 arrFiles[High(arrFiles)].FullName:=Path+sr.Name;
 len:=len+Length(Path+sr.Name);
 Label1.Caption:=inttostr(len);
 Application.ProcessMessages;
 arrFiles[High(arrFiles)].Size:=sr.Size;
end;
end;

procedure TfmMain.Button1Click(Sender: TObject);
begin
fDoRecursPath:=True;
ScanPath('D:\', 'D:', AddFilesToArray);
end;

end.


Сергей М.   (2007-10-11 16:55:00) [35]


> Есть динамический массив, в который помещаются записи (record)
> с описанием файлов при сканировании диска


Довольно глупо хранить данные древовиднойструктуры в контейнере линейнойструктуры.


Суслик   (2007-10-11 16:52:00) [36]


>  [3] vint45   (11.10.07 16:35)
> Под массив память выделяю через SetLength (SetLength(arrFiles,
> Length(arrFiles)+1);)
> Память под строки выдяется через заполнение структуры TSearchRec.
> Запись идет через простое присвоение:
> arrFiles[High(arrFiles)].FullName:=sr.Name;


все ок. где-то ты ошибаешься в оценке объемов - либо общего кол-ва записей, либо длинн строк.


Ping   (2007-10-11 16:48:00) [37]

неэффективно

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


Ping   (2007-10-11 16:45:00) [38]

И дублирующиеся, дублирующиеся папки. То, что строки в Delphi могут быть расшаренными, частично спасает положение. Но как-то это "некрасиво".

Размещай папки в такие же записи, а у вложенных файлов и папок - сделай ссылку на родительскую папку.


clickmaker   (2007-10-11 16:45:00) [39]


> SetLength (SetLength(arrFiles, Length(arrFiles)+1);)

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


vint45   (2007-10-11 16:35:00) [40]

Под массив память выделяю через SetLength ( SetLength(arrFiles, Length(arrFiles)+1);)
Память под строки выдяется через заполнение структуры TSearchRec.
Запись идет через простое присвоение:
arrFiles[High(arrFiles)].FullName:=sr.Name;


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо