[Lazarus] Writing >1000 TBufDataset records to file is extremely slow

Werner Pamler werner.pamler at freenet.de
Mon Mar 27 10:04:59 CEST 2017


Am 27.03.2017 um 00:53 schrieb Howard Page-Clark via Lazarus:
> I can get small performance increases by
> - avoiding FieldByName() calls and using AppendRecord
No, at least not for the issue I am referring to. Like in the answer to 
Marc's comment: This happens while the table is populated, but the delay 
occurs when the populated table is written to stream/file.

> - using SaveToFile and avoiding an intermediate memory stream
Has no noticable effect. 1000 records really is not much, and flushing 
the memory stream to disk occurs without any delay (see my code haveing 
measurement points before and after writing the memory stream and after 
writing to file. In fact, when I noticed this effect I did not have any 
explicit writing code at all, I noticed an excessive delay while the 
dataset is closed -- this is when the BufDataset is saved automatically.

> - increasing the value of PacketRecords
Not knowing what this is I increased the value in multiples of 10 from 1 
to 1E9 and don't see any effect within the usual scatter.

> Clearly either the insertion algorithm should be improved, or the 
> buffering, or the way the buffered records are written to disk. Maybe 
> all three areas of TBufDataset can be optimised for better performance.
Thanks. When I have time I'll write a bug report. The current 
TBufDataset is usable only as a pure in-memory table which is never 
written to file. BTW, in the attached modified demo code the TBufDataset 
can be replaced by a TMemDataset (define "USE_MEM_DATASET"), and this 
one is written instantly.

------------------ snip -------------------

program project1;

{$mode objfpc}{$H+}

{$DEFINE USE_MEM_DATASET}

uses
   SysUtils, classes, db, memds, bufdataset;

const
   TABLENAME = 'people'; //name for the database table, extension will 
be added
   DATADIR = 'data'; //subdirectory where database is stored

const
   NUM_RECORDS = 5000;
   SECONDS_PER_DAY = 24 * 60 * 60;

var
   FExportDataset: TDataset;

procedure CreateDatabase;
var
   i: Integer;
   fn: String;
   stream: TMemoryStream;
   t: TDateTime;
begin
   ForceDirectories(DATADIR);

   fn := DATADIR + DirectorySeparator + TABLENAME + '.db';
   DeleteFile(fn);

   {$IFDEF USE_MEM_DATASET}
   FExportDataset := TMemDataset.Create(nil);
   {$ELSE}
   FExportDataset := TBufDataset.Create(nil);
   {$ENDIF}

   FExportDataset.FieldDefs.Add('Last name', ftString, 15);
   FExportDataset.FieldDefs.Add('First name', ftString, 10);
   FExportDataset.FieldDefs.Add('City', ftString, 15);
   FExportDataset.FieldDefs.Add('Birthday', ftDate);
   FExportDataset.FieldDefs.Add('Salary', ftCurrency);
   FExportDataset.FieldDefs.Add('Work begin', ftDateTime);
   FExportDataset.FieldDefs.Add('Work end', ftDateTime);
   FExportDataset.FieldDefs.Add('Size', ftFloat);
   {$IFNDEF USE_MEM_DATASET}
   TBufDataset(FExportDataset).CreateDataset;
   {$ENDIF}

   FExportDataset.Open;

   // Random data
   for i:=1 to NUM_RECORDS do begin
     if (i mod 100 = 0) then
       WriteLn(Format('Adding record %d...', [i]));
     FExportDataset.Insert;
     FExportDataset.FieldByName('Last name').AsString := 'A';
     FExportDataset.FieldByName('First name').AsString := 'B';
     FExportDataset.FieldByName('City').AsString := 'C';
     FExportDataset.FieldByName('Birthday').AsDateTime := 0;
     FExportDataset.FieldByName('Salary').AsFloat := 0;
     FExportDataset.FieldByName('Size').AsFloat := 0;
     FExportDataSet.FieldByName('Work begin').AsDateTime := 0;
     FExportDataSet.FieldByName('Work end').AsDateTime := 0;
     FExportDataset.Post;
   end;

   WriteLn('Saving...');
   t := now;
   stream := TMemoryStream.Create;
   try
     {$IFDEF USE_MEM_DATASET}
     TMemDataset(FExportDataset).SaveToStream(stream);
     {$ELSE}
     TBufDataset(FExportDataset).SaveToStream(stream);
     {$ENDIF}
     stream.Position := 0;
     WriteLn('Written to memory stream: ', FormatFloat('0.000 s', (now - 
t) * SECONDS_PER_DAY));
     stream.SaveToFile(fn);
   finally
     stream.Free;
   end;
   Writeln('Done. Total time needed for saving: ', FormatFloat('0.000 
s', (now - t) * SECONDS_PER_DAY));

   FExportDataset.Close;

   writeLn(Format('Created file "%s" in folder "data".', [
     ExtractFileName(fn), ExtractFileDir(fn)
   ]));
   FExportDataset.Free;
end;

begin
   CreateDatabase;

   WriteLn;
   WriteLn('Press ENTER to close.');
   ReadLn;
end.



More information about the Lazarus mailing list