Fraction
asked on
FindFirst/Next to slow
Hi!
I've tried several sort routines and I even tried to skip sorting, and still it takes some time to process large directories. Now I beleve it's the FindFirst/Next functions that's slow?
Is there a faster way to create a file-list?
I've tried several sort routines and I even tried to skip sorting, and still it takes some time to process large directories. Now I beleve it's the FindFirst/Next functions that's slow?
Is there a faster way to create a file-list?
ASKER
I need more information to be displayed to each file than the FileListBox control offers, size, date, attrib, etc.
I store all file-info in a datastructure, sort it using the common combsort routine before adding it to a TListView (with the BeginUpdate turned on). The TListView update seems to be quiet fast, running without the sort routine does not affect the performance that much, I really think it's then reading that slows things down.
I store all file-info in a datastructure, sort it using the common combsort routine before adding it to a TListView (with the BeginUpdate turned on). The TListView update seems to be quiet fast, running without the sort routine does not affect the performance that much, I really think it's then reading that slows things down.
Hmmm I don't know but I really think the FindFirst/Next is rather fast. I have used it for moving across the entire disk and I found it to be as fast as anything else I have seen. I think as DrDelphi said it probably has to do with how you are passing the info to your controls. :>)
You might look into this API. I have never used it so I don't have any code to show you on it.
SHGetFileInfo
The Crazy One
You might look into this API. I have never used it so I don't have any code to show you on it.
SHGetFileInfo
The Crazy One
Maybe you could post your code snippet, so we could try to figure out where it goes wrong.
ASKER
I'm Looking on the SHGetFileInfo, anyway, here is a snipped piece of my code (did'nt include the sort routine, but that part is fast):
//-- Begin ----------->
//-- Data structure to store files info -----------
type
TDirItem = record
Icon: integer;
Name: string;
Size: int64;
Date: TDateTime;
Attr: integer;
end;
PDirItems = ^TDirItems;
TDirItems = record
Count: integer;
Items: array[0..4000] of TDirItem;
end;
//-- The ListView Update routines (Quiet fast)-----------
procedure UpdateFileList;
var
i: integer;
cr: TCursor;
Name, Ext, Size, SDate, FAttr: string;
begin
cr := Screen.Cursor;
Screen.Cursor := crHourGlass;
FileList.Items.BeginUpdate ;
FileList.Items.Clear;
FileList.Columns.Clear;
DirPanel.Caption := FileListDir;
for i:=0 to 4 do begin
with FileList.Columns.Add do begin
Caption := DefColCaption[i].Caption;
Width := DefColCaption[i].Width;
Alignment := DefColCaption[i].Align;
Tag := i;
OnClick := ColumnClick;
end;
end;
if FileListContent^.Count>0 then begin
for i := 0 to FileListContent^.Count - 1 do begin
if (FileListContent^.Items[i] .Attr and faDirectory)<>faDirectory then begin
Ext := ExtractFileExt(FileListCon tent^.Item s[i].Name) ;
Name := FileListContent^.Items[i]. Name;
if Ext>'' then begin
delete(Name, Length(Name)-Length(Ext)+1 , length(Ext));
delete(Ext, 1, 1);
end;
Size := SizeToString(FileListConte nt^.Items[ i].Size);
end
else begin
Ext := '';
Name := FileListContent^.Items[i]. Name;
Size := '<DIR>';
end;
SDate := FormatDateTime(DateFormat, FileListContent^.Items[i]. Date);
if (FileListContent^.Items[i] .Attr and faReadOnly)=faReadOnly then FAttr := 'r' else FAttr := '-';
if (FileListContent^.Items[i] .Attr and faArchive)=faArchive then FAttr := FAttr + 'a' else FAttr := FAttr + '-';
if (FileListContent^.Items[i] .Attr and faHidden)=faHidden then FAttr := FAttr + 'h' else FAttr := FAttr + '-';
if (FileListContent^.Items[i] .Attr and faSysFile)=faSysFile then FAttr := FAttr + 's' else FAttr := FAttr + '-';
with FileList.Items.Add do begin
ImageIndex := FileListContent^.Items[i]. Icon;
Caption := Name;
SubItems.Add(Ext);
SubItems.Add(Size);
SubItems.Add(SDate);
SubItems.Add(FAttr);
end;
end;
end;
Screen.Cursor := cr;
FileList.Items.EndUpdate;
end;
//-- The Read Dir routines (Slow)-----------
prodecure ReadDir(Dir: string);
var
i: integer;
SR: TSearchRec;
FileInfo: TSHFILEINFO;
begin
if Dir>'' then begin
try
ChDir(Dir);
except
ShowMess(false, ic_Info, Format('"%s" - Path not found!', [Dir]));
exit;
end;
if Dir[Length(Dir)]<>'\' then Dir := Dir + '\';
i := Ord(UpCase(Dir[1])) - 65;
Paths[i] := Dir;
FileListContent^.Count := 0;
i := FindFirst(Dir + '*.*', faAnyFile, SR);
while i=0 do begin
if ((SR.Attr and faDirectory)=faDirectory) and (SR.Name <> '.') then begin
inc(FileListContent^.Count );
FileListContent^.Items[Fil eListConte nt^.Count- 1].Name := SR.Name;
FileListContent^.Items[Fil eListConte nt^.Count- 1].Size := 0;
FileListContent^.Items[Fil eListConte nt^.Count- 1].Date := FileDateToDateTime(SR.Time );
FileListContent^.Items[Fil eListConte nt^.Count- 1].Attr := SR.Attr;
end
else if SR.Name<>'.' then begin
inc(FileListContent^.Count );
FileListContent^.Items[Fil eListConte nt^.Count- 1].Name := SR.Name;
FileListContent^.Items[Fil eListConte nt^.Count- 1].Size := SR.Size;
FileListContent^.Items[Fil eListConte nt^.Count- 1].Date := FileDateToDateTime(SR.Time );
FileListContent^.Items[Fil eListConte nt^.Count- 1].Attr := SR.Attr;
end;
if SR.Name <> '.' then begin
if SR.Name = '..' then FileListContent^.Items[Fil eListConte nt^.Count- 1].Icon := 4
else begin
ShGetFileInfo(PChar(Dir+SR .Name), 0, FileInfo, SizeOf(FileInfo),SHGFI_SMA LLICON or SHGFI_SYSICONINDEX or SHGFI_TYPENAME);
FileListContent^.Items[Fil eListConte nt^.Count- 1].Icon := FileInfo.iIcon;
end;
end;
i := FindNext(SR);
end;
FindClose(SR);
Sort;
end;
UpdateFileList;
end;
//-- End ----------->
//-- Begin ----------->
//-- Data structure to store files info -----------
type
TDirItem = record
Icon: integer;
Name: string;
Size: int64;
Date: TDateTime;
Attr: integer;
end;
PDirItems = ^TDirItems;
TDirItems = record
Count: integer;
Items: array[0..4000] of TDirItem;
end;
//-- The ListView Update routines (Quiet fast)-----------
procedure UpdateFileList;
var
i: integer;
cr: TCursor;
Name, Ext, Size, SDate, FAttr: string;
begin
cr := Screen.Cursor;
Screen.Cursor := crHourGlass;
FileList.Items.BeginUpdate
FileList.Items.Clear;
FileList.Columns.Clear;
DirPanel.Caption := FileListDir;
for i:=0 to 4 do begin
with FileList.Columns.Add do begin
Caption := DefColCaption[i].Caption;
Width := DefColCaption[i].Width;
Alignment := DefColCaption[i].Align;
Tag := i;
OnClick := ColumnClick;
end;
end;
if FileListContent^.Count>0 then begin
for i := 0 to FileListContent^.Count - 1 do begin
if (FileListContent^.Items[i]
Ext := ExtractFileExt(FileListCon
Name := FileListContent^.Items[i].
if Ext>'' then begin
delete(Name, Length(Name)-Length(Ext)+1
delete(Ext, 1, 1);
end;
Size := SizeToString(FileListConte
end
else begin
Ext := '';
Name := FileListContent^.Items[i].
Size := '<DIR>';
end;
SDate := FormatDateTime(DateFormat,
if (FileListContent^.Items[i]
if (FileListContent^.Items[i]
if (FileListContent^.Items[i]
if (FileListContent^.Items[i]
with FileList.Items.Add do begin
ImageIndex := FileListContent^.Items[i].
Caption := Name;
SubItems.Add(Ext);
SubItems.Add(Size);
SubItems.Add(SDate);
SubItems.Add(FAttr);
end;
end;
end;
Screen.Cursor := cr;
FileList.Items.EndUpdate;
end;
//-- The Read Dir routines (Slow)-----------
prodecure ReadDir(Dir: string);
var
i: integer;
SR: TSearchRec;
FileInfo: TSHFILEINFO;
begin
if Dir>'' then begin
try
ChDir(Dir);
except
ShowMess(false, ic_Info, Format('"%s" - Path not found!', [Dir]));
exit;
end;
if Dir[Length(Dir)]<>'\' then Dir := Dir + '\';
i := Ord(UpCase(Dir[1])) - 65;
Paths[i] := Dir;
FileListContent^.Count := 0;
i := FindFirst(Dir + '*.*', faAnyFile, SR);
while i=0 do begin
if ((SR.Attr and faDirectory)=faDirectory) and (SR.Name <> '.') then begin
inc(FileListContent^.Count
FileListContent^.Items[Fil
FileListContent^.Items[Fil
FileListContent^.Items[Fil
FileListContent^.Items[Fil
end
else if SR.Name<>'.' then begin
inc(FileListContent^.Count
FileListContent^.Items[Fil
FileListContent^.Items[Fil
FileListContent^.Items[Fil
FileListContent^.Items[Fil
end;
if SR.Name <> '.' then begin
if SR.Name = '..' then FileListContent^.Items[Fil
else begin
ShGetFileInfo(PChar(Dir+SR
FileListContent^.Items[Fil
end;
end;
i := FindNext(SR);
end;
FindClose(SR);
Sort;
end;
UpdateFileList;
end;
//-- End ----------->
ASKER
PS (I forgott):
var
FileListContent: PDirItems;
var
FileListContent: PDirItems;
I believe that the call to SHGetFileInfo is slowing things down; you have most information you need already in the search record (attributes, size, name, date...), so try to avoid calling it.
Using WITH could also help the compiler generate better code; instead of:
if ((SR.Attr and faDirectory)=faDirectory) and (SR.Name <> '.') then begin
inc(FileListContent^.Count );
FileListContent^.Items[Fil eListConte nt^.Count- 1].Name := SR.Name;
FileListContent^.Items[Fil eListConte nt^.Count- 1].Size := 0;
FileListContent^.Items[Fil eListConte nt^.Count- 1].Date := FileDateToDateTime(SR.Time );
FileListContent^.Items[Fil eListConte nt^.Count- 1].Attr := SR.Attr;
end
try this:
with FileListContent^ do
if ((SR.Attr and faDirectory)=faDirectory) and (SR.Name <> '.') then begin
with Items[Count] do begin
Name := SR.Name;
Size := 0;
FileDateToDateTime(SR.Time );
Attr := SR.Attr;
end;
inc(Count);
end
Also be aware that FindClose should only be called upon a successfull FindFirst; if FindFirst was unsuccessful, you should NOT call FindClose on that search record. Therefore, rather do your search like this:
if FindFirst([...],SR)=0 then try
repeat
[...]
until FindNext(SR)<>0;
finally
FindClose(SR);
end;
Using WITH could also help the compiler generate better code; instead of:
if ((SR.Attr and faDirectory)=faDirectory) and (SR.Name <> '.') then begin
inc(FileListContent^.Count
FileListContent^.Items[Fil
FileListContent^.Items[Fil
FileListContent^.Items[Fil
FileListContent^.Items[Fil
end
try this:
with FileListContent^ do
if ((SR.Attr and faDirectory)=faDirectory) and (SR.Name <> '.') then begin
with Items[Count] do begin
Name := SR.Name;
Size := 0;
FileDateToDateTime(SR.Time
Attr := SR.Attr;
end;
inc(Count);
end
Also be aware that FindClose should only be called upon a successfull FindFirst; if FindFirst was unsuccessful, you should NOT call FindClose on that search record. Therefore, rather do your search like this:
if FindFirst([...],SR)=0 then try
repeat
[...]
until FindNext(SR)<>0;
finally
FindClose(SR);
end;
ASKER
You got a point there Avon, is there a simpler way of finding out icon index of a filetype other than using SHGetFileInfo?
Right now I'm looking at D5 demo "Virtual Listview", fast as lightning, hard to understand, but I think this is the piece of code I want.
Right now I'm looking at D5 demo "Virtual Listview", fast as lightning, hard to understand, but I think this is the piece of code I want.
ASKER
Sorry, You got a few points there. My code is quiet uggly, I know ;-)
This is what I use
procedure GetFiles(List : TStrings; ASearchString : String; SubDirs : Boolean; IncludeDirs : Boolean = False);
var
R : Integer;
SR : TSearchRec;
Dir,
Mask : String;
begin
if List = nil then
raise Exception.Create('Stringli st has not been created.');
List.Clear;
//Get files
R := FindFirst(aSearchString, faAnyFile - faDirectory, SR);
while R = 0 do begin
List.Add(ExtractFilePath(a SearchStri ng) + SR.Name);
R := FindNext(SR);
end;
FindClose(SR);
//Get directories
if SubDirs or IncludeDirs then begin
R := FindFirst(ExtractFilePath( aSearchStr ing) + '*.*', faDirectory, SR);
while R = 0 do begin
if SR.Attr and faDirectory <> 0 then begin
Dir := ExtractFilePath(aSearchStr ing);
Mask := ExtractFileName(aSearchStr ing);
if (SR.Name <> '.') and (SR.Name <> '..') then begin
if IncludeDirs then List.Add(Dir + SR.Name + '\');
If SubDirs then GetFiles(List, Dir + SR.Name + '\' + Mask, True);
end;
end;
R := FindNext(SR);
end;
FindClose(SR);
end;
end;
Try that and see if it is fast or not.
If it is slow then it is the ListView. I believe ListViews + Delphi are slow anyway.
Pete
====
http://www.HowToDoThings.com (Articles)
http://www.Stuckindoors.com/delphi (Open source)
procedure GetFiles(List : TStrings; ASearchString : String; SubDirs : Boolean; IncludeDirs : Boolean = False);
var
R : Integer;
SR : TSearchRec;
Dir,
Mask : String;
begin
if List = nil then
raise Exception.Create('Stringli
List.Clear;
//Get files
R := FindFirst(aSearchString, faAnyFile - faDirectory, SR);
while R = 0 do begin
List.Add(ExtractFilePath(a
R := FindNext(SR);
end;
FindClose(SR);
//Get directories
if SubDirs or IncludeDirs then begin
R := FindFirst(ExtractFilePath(
while R = 0 do begin
if SR.Attr and faDirectory <> 0 then begin
Dir := ExtractFilePath(aSearchStr
Mask := ExtractFileName(aSearchStr
if (SR.Name <> '.') and (SR.Name <> '..') then begin
if IncludeDirs then List.Add(Dir + SR.Name + '\');
If SubDirs then GetFiles(List, Dir + SR.Name + '\' + Mask, True);
end;
end;
R := FindNext(SR);
end;
FindClose(SR);
end;
end;
Try that and see if it is fast or not.
If it is slow then it is the ListView. I believe ListViews + Delphi are slow anyway.
Pete
====
http://www.HowToDoThings.com (Articles)
http://www.Stuckindoors.com/delphi (Open source)
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thanx Avon! This really speed things up.
Sometimes the solution is to simple to figure out ;-)
Sometimes the solution is to simple to figure out ;-)
Glad to help!
1: If you are rendering this directory in a listbox (or some other windowed control) as you are building it, you might want to consider retrieving all the files first into an offscreen reposititory such as a TstringList and then assign that to you windowed control once it is done. This way you avoid constantly having to refresh the Listbox with each new file added. This some help performance.
2. There is a really decent FileListBox control which ships with Delphi. It's under the Win3.1 tab.
Good luck!!