![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Резкое увеличение размера динамического массива [D6, WinXP]
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;