Reputation: 3402
My program is processing incoming strings (from Telnet, HTTP, etc), and I have to write these to a text file with Delphi XE2 for logging purposes.
Sometimes the program may crash and I need to be sure that the remaining strings are not lost so I open/close the file for every incoming string and I have some performance problems. The code below, for example, takes 8 seconds to complete.
My code is included below, is there some way to improve the performance?
(For the test below simply create a Form with a Button : Button1
, with OnClick
event and a Label : lbl1
).
Procedure AddToFile(Source: string; FileName :String);
var
FText : Text;
TmpBuf: array[word] of byte;
Begin
{$I-}
AssignFile(FText, FileName);
Append(FText);
SetTextBuf(FText, TmpBuf);
Writeln(FText, Source);
CloseFile(FText);
{$I+}
end;
procedure initF(FileName : string);
Var FText : text;
begin
{$I-}
if FileExists(FileName) then DeleteFile(FileName);
AssignFile(FText, FileName);
ReWrite(FText);
CloseFile(FText);
{$I+}
end;
procedure TForm1.Button1Click(Sender: TObject);
var tTime : TDateTime;
iBcl : Integer;
FileName : string;
begin
FileName := 'c:\Test.txt';
lbl1.Caption := 'Go->' + FileName; lbl1.Refresh;
initF(FileName);
tTime := Now;
For iBcl := 0 to 2000 do
AddToFile(IntToStr(ibcl) + ' ' + 'lkjlkjlkjlkjlkjlkjlkj' , FileName);
lbl1.Caption := FormatDateTime('sss:zzz',Now-tTime);
end;
Upvotes: 6
Views: 10375
Reputation: 11
for some reason a simple reading from one text file and writing to text output file I found the TextFile WriteLn is still the fastest way.
AssignFile(t,'c:\a\in.csv');
Reset(t);
AssignFile(outt,'c:\a\out.csv');
ReWrite(outt);
while not eof(t) do
begin
Readln(t,x);
WriteLn(outt, x); //27 sec, using LogSW.WriteLine(outx) takes 54 sec
// half Gb file took 27 sec with the above code, using TStreamWriter from example provided by Martijn took 54 seconds :o
Upvotes: 1
Reputation: 125620
Use a TStreamWriter
, which is automatically buffered, and can handle flushing its buffers to the TFileStream
automatically. It also allows you to choose to append to an existing file if you need to, set character encodings for Unicode support, and lets you set a different buffer size (the default is 1024 bytes, or 1K) in its various overloaded Create
constructors.
(Note that flushing the TStreamWriter
only writes the content of the TStreamBuffer
to the TFileStream
; it doesn't flush the OS file system buffers, so the file isn't actually written on disk until the TFileStream
is freed.)
Don't create the StreamWriter every time; just create and open it once, and close it at the end:
function InitLog(const FileName: string): TStreamWriter;
begin
Result := TStreamWriter.Create(FileName, True);
Result.AutoFlush := True; // Flush automatically after write
Result.NewLine := sLineBreak; // Use system line breaks
end;
procedure CloseLog(const StreamWriter: TStreamWriter);
begin
StreamWriter.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
tTime : TDateTime;
iBcl : Integer;
LogSW: TStreamWriter;
FileName: TFileName;
begin
FileName := 'c:\Test.txt';
LogSW := InitLog(FileName);
try
lbl1.Caption := 'Go->' + FileName;
lbl1.Refresh;
tTime := Now;
For iBcl := 0 to 2000 do
LogSW.WriteLine(IntToStr(ibcl) + ' ' + 'lkjlkjlkjlkjlkjlkjlkj');
lbl1.Caption := FormatDateTime('sss:zzz',Now - tTime);
finally
CloseLog(LogSW);
end;
end;
Upvotes: 15
Reputation: 3932
It seems your problem is that you need to flush the cache after each write so that you won't lose data if your application crashes.
Whereas I'm sure the other answers here are excellent, you needn't make such extensive changes to your code. All you need to do is call Flush(FText)
after each write.
const
// 10 million tests
NumberOfTests = 1000000;
// Open and close with each write: 19.250 seconds
// Open once, and flush after each write: 5.686 seconds
// Open once, don't flush 0.439 seconds
var
FText : Text;
TmpBuf: array[word] of byte;
procedure initF(FileName : string);
begin
{$I-}
if FileExists(FileName) then DeleteFile(FileName);
AssignFile(FText, FileName);
ReWrite(FText);
SetTextBuf(FText, TmpBuf);
{$I+}
end;
procedure CloseTheFile;
begin
CloseFile(FText);
end;
Procedure AddToFile(Source: string);
Begin
{$I-}
Writeln(FText, Source);
// flush the cache after each write so that data will be written
// even if program crashes.
flush ( fText ); // <<<==== Flush the Cache after each write
{$I+}
end;
procedure TForm1.Button1Click(Sender: TObject);
var tTime : TDateTime;
iBcl : Integer;
FileName : string;
begin
FileName := 'c:\Test.txt';
lbl1.Caption := 'Go->' + FileName; lbl1.Refresh;
initF(FileName);
// put file close in a try/finally block to ensure file is closed
// even if an exception is raised.
try
tTime := Now;
For iBcl := 0 to NumberOfTests-1 do
AddToFile(IntToStr(ibcl) + ' ' + 'lkjlkjlkjlkjlkjlkjlkj');
lbl1.Caption := FormatDateTime('sss:zzz',Now-tTime);
finally
CloseTheFile;
end;
end;
Upvotes: 2
Reputation: 27493
Instead of reopening file to save critical data on disk you can either use FlushFileBuffers function or open a file for unbuffered I/O by calling the CreateFile function with the FILE_FLAG_NO_BUFFERING
and FILE_FLAG_WRITE_THROUGH
flags (see Remarks
section in the first link).
Upvotes: 3