Page 1 of 2 1 2 LastLast
Results 1 to 15 of 17

Thread: CSV bestanden sneller uitlezen

  1. #1
    Hobby programmeur
    Join Date
    Jul 2007
    Location
    De Bilt
    Posts
    262

    CSV bestanden sneller uitlezen

    Ik heb een aantal *.csv bestanden waarin de status van elk item word bijgehouden.
    Zo'n bestand kan rond de 10.000 rijen bevatten. Hieronder een klein voorbeeld van de inhoud:
    Versie Categorie Leverancier Item Datum Status
    V1-0 PRODUCT-1 TEST 1 1-4-2019
    V1-0 PRODUCT-1 TEST 2 1-4-2019
    V1-0 PRODUCT-1 TEST 3 1-4-2019
    V1-0 PRODUCT-1 TEST 4 1-4-2019
    V1-0 PRODUCT-1 TEST 5 1-4-2019
    V1-0 PRODUCT-1 TEST 6 1-4-2019
    V1-0 PRODUCT-1 TEST 7 1-4-2019
    V1-0 PRODUCT-1 TEST 8 1-4-2019
    V1-0 PRODUCT-1 TEST 9 1-4-2019
    V1-0 PRODUCT-1 TEST 10 3-4-2019 Geleverd
    V1-0 PRODUCT-1 TEST 11 3-4-2019 Geleverd
    V1-0 PRODUCT-1 TEST 12 3-4-2019 Geleverd
    V1-0 PRODUCT-1 TEST 13 3-4-2019 Geleverd
    V1-0 PRODUCT-1 TEST 14 3-4-2019 Geleverd
    Nu wil ik graag drie van dat soort *.csv bestanden uitlezen met behulp van TCSVdocument. De bestanden wordt tijdens het starten van het programma al ingeladen. Ik heb ook drie TListviews aangemaakt die ik straks wil gaan vullen met de gevonden resultaten per *.csv bestand. Van elk bestand ga ik vervolgens controleren welke rijen de status "geleverd" bevatten. Wanneer aan deze voorwaarde is voldaan, wil ik de bijbehorende TListview vullen met als resultaat het item nummer en datum uit de gevonden rij.

    Hiervoor gebruik ik de volgende code:
    Delphi Code:
    1. Procedure Zoek_Geleverde_Items(Input: TCSVdocument; Aantal_lbl: TLabel; Lijst: TListView);
    2. var
    3.   i: Integer;
    4.   StatusKolom: Integer;
    5.   ItemKolom: Integer;
    6.   DatumKolom: Integer;
    7.   Status: String;
    8. begin
    9.   StatusKolom := Input.IndexOfCol('Status', 0);
    10.   ItemKolom := Input.IndexOfCol('Item', 0);
    11.   DatumKolom := Input.IndexOfCol('Datum', 0);
    12.   for i := 1 to Input.RowCount -1 do
    13.    begin
    14.      Status := Input.Cells[StatusKolom, i];
    15.      if Status = 'Geleverd' then
    16.        with Lijst.Items.Add do
    17.         begin
    18.           Caption := Input.Cells[ItemKolom, i];
    19.           SubItems.Add(Input.Cells[DatumKolom, i]);
    20.         end;
    21.    end;
    22.    Aantal_lbl.Caption := 'Aantal: ' + IntToStr(Lijst.Items.Count);
    23. end;
    Welke wordt aangeroepen via een button:
    Delphi Code:
    1. procedure TForm1.Button1Click(Sender: TObject);
    2. var
    3.   cStart, cStop: Cardinal;
    4. begin
    5.   Screen.Cursor := crHourGlass;
    6.   cStart := GetTickCount64;
    7.   Listview1.BeginUpdate;
    8.   Listview2.BeginUpdate;
    9.   Listview3.BeginUpdate;
    10.   try
    11.     Listview1.Clear;
    12.     Listview2.Clear;
    13.     Listview3.Clear;
    14.     Zoek_Geleverde_Items(Product1, Label1, Listview1);
    15.     Zoek_Geleverde_Items(Product2, Label2, Listview2);
    16.     Zoek_Geleverde_Items(Product3, Label3, Listview3);
    17.     cStop := GetTickCount64-cStart;
    18.     Showmessage(IntToStr(cStop));
    19.   finally
    20.     Listview1.EndUpdate;
    21.     Listview2.EndUpdate;
    22.     Listview3.EndUpdate;
    23.     Screen.Cursor := crDefault;
    24.   end;
    25. end;

    Volledige source:
    Delphi Code:
    1. unit Unit1;
    2.  
    3. {$mode objfpc}{$H+}
    4.  
    5. interface
    6.  
    7. uses
    8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls,
    9.   CSVdocument;
    10.  
    11. type
    12.  
    13.   { TForm1 }
    14.  
    15.   TForm1 = class(TForm)
    16.     Button1: TButton;
    17.     GroupBox1: TGroupBox;
    18.     GroupBox2: TGroupBox;
    19.     GroupBox3: TGroupBox;
    20.     Label1: TLabel;
    21.     Label2: TLabel;
    22.     Label3: TLabel;
    23.     ListView1: TListView;
    24.     ListView2: TListView;
    25.     ListView3: TListView;
    26.     procedure Button1Click(Sender: TObject);
    27.     procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    28.     procedure FormCreate(Sender: TObject);
    29.   private
    30.     Product1: TCSVdocument;
    31.     Product2: TCSVdocument;
    32.     Product3: TCSVdocument;
    33.   public
    34.  
    35.   end;
    36.  
    37. var
    38.   Form1: TForm1;
    39.  
    40. implementation
    41.  
    42. {$R *.lfm}
    43.  
    44. { TForm1 }
    45.  
    46. Procedure Zoek_Geleverde_Items(Input: TCSVdocument; Aantal_lbl: TLabel; Lijst: TListView);
    47. var
    48.   i: Integer;
    49.   StatusKolom: Integer;
    50.   ItemKolom: Integer;
    51.   DatumKolom: Integer;
    52.   Status: String;
    53. begin
    54.   StatusKolom := Input.IndexOfCol('Status', 0);
    55.   ItemKolom := Input.IndexOfCol('Item', 0);
    56.   DatumKolom := Input.IndexOfCol('Datum', 0);
    57.   for i := 1 to Input.RowCount -1 do
    58.    begin
    59.      Status := Input.Cells[StatusKolom, i];
    60.      if Status = 'Geleverd' then
    61.        with Lijst.Items.Add do
    62.         begin
    63.           Caption := Input.Cells[ItemKolom, i];
    64.           SubItems.Add(Input.Cells[DatumKolom, i]);
    65.         end;
    66.    end;
    67.    Aantal_lbl.Caption := 'Aantal: ' + IntToStr(Lijst.Items.Count);
    68. end;
    69.  
    70. procedure TForm1.Button1Click(Sender: TObject);
    71. var
    72.   cStart, cStop: Cardinal;
    73. begin
    74.   Screen.Cursor := crHourGlass;
    75.   cStart := GetTickCount64;
    76.   Listview1.BeginUpdate;
    77.   Listview2.BeginUpdate;
    78.   Listview3.BeginUpdate;
    79.   try
    80.     Listview1.Clear;
    81.     Listview2.Clear;
    82.     Listview3.Clear;
    83.     Zoek_Geleverde_Items(Product1, Label1, Listview1);
    84.     Zoek_Geleverde_Items(Product2, Label2, Listview2);
    85.     Zoek_Geleverde_Items(Product3, Label3, Listview3);
    86.     cStop := GetTickCount64-cStart;
    87.     Showmessage(IntToStr(cStop));
    88.   finally
    89.     Listview1.EndUpdate;
    90.     Listview2.EndUpdate;
    91.     Listview3.EndUpdate;
    92.     Screen.Cursor := crDefault;
    93.   end;
    94. end;
    95.  
    96. procedure TForm1.FormCreate(Sender: TObject);
    97. var
    98.   Bestand1: String;
    99.   Bestand2: String;
    100.   Bestand3: String;
    101. begin
    102.     Bestand1 := ExtractFilePath(Application.ExeName) + 'Data\Product_1.csv';
    103.     Bestand2 := ExtractFilePath(Application.ExeName) + 'Data\Product_2.csv';
    104.     Bestand3 := ExtractFilePath(Application.ExeName) + 'Data\Product_3.csv';
    105.  
    106.     Product1 := TCSVdocument.Create;
    107.     Product1.Delimiter:= ';';
    108.     if FileExists(Bestand1) then
    109.       Product1.LoadFromFile(Bestand1);
    110.  
    111.     Product2 := TCSVdocument.Create;
    112.     Product2.Delimiter:= ';';
    113.     if FileExists(Bestand2) then
    114.       Product2.LoadFromFile(Bestand2);
    115.  
    116.     Product3 := TCSVdocument.Create;
    117.     Product3.Delimiter:= ';';
    118.     if FileExists(Bestand3) then
    119.       Product3.LoadFromFile(Bestand3);
    120. end;
    121.  
    122. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
    123. begin
    124.     Product1.Free;
    125.     Product2.Free;
    126.     Product3.Free;
    127. end;
    128.  
    129. end.

    Dit werkt prima maar duurt nu een kleine 6/7 seconden voordat alles is doorlopen en de Listviews zijn gevuld.
    Zou dit sneller kunnen? Mijn i5 processor gebruikt nu namelijk maar 25% aan rekenkracht. Mogelijk dat er meer winst te behalen valt met Multi Threading? Ik heb daar echter geen ervaring mee en ben benieuwd of dit parallel uitgevoerd kan worden? Daarmee zou deze taak 3x sneller kunnen worden lijkt mij.
    Last edited by sandertje; 22-Nov-19 at 00:00.

  2. #2
    Het is ondertussen 9 jaar geleden dat ik voor het laatst met TListView heb gewerkt, maar ik kan me herinneren dat de manier om zeer snel met listviews te werken is door virtual listviews te gebruiker (zet OwnerData property als True). Eigenlijk zet je dan enkel het aantal rijen zonder de gegevens oom echt in de listview te laden. En pas bij het OnData event laad je de gegevens. Als er op het scherm bijvoorbeeld 50 rijen zijn dan worden enkel die geladen...tot de gebruiker gaat scrollen en nieuwe rijen verschijnen. Met wat Googlen zou je snel verder geholpen kunnen worden.

    Natuurlijk is multi threading altijd leuk, maar brengt ook andere uitdagingen mee. Ik denk dat Virtual Listview je quick win is.

  3. #3
    Virtual Listview lijkt mij ook de "way to go" - ik heb zelf ook eens een applicatie gehad, waar ik regelmatig 10.000-100.000 items had in een listview - wat de boel enorm traag maakte, maar met virtual listview gaat t supersnel!

  4. #4
    Hobby programmeur
    Join Date
    Jul 2007
    Location
    De Bilt
    Posts
    262
    Bedankt voor de tip! Het gebruik van Virtual Listview maakt een wereld van verschil.
    Op dit forum kwam ik een prachtig voorbeeld van Virtual Listview tegen. Deze heb ik toegepast en nu zijn al mijn drie Listviews binnen 16ms gevuld!

    Nu valt het meer op dat het laden van een *.csv bestand d.m.v. TCSVDocument.LoadFromFile() betrekkelijk langzaam is. Ik ga eens rondkijken of daar nog snelheidswinst op te behalen valt.

  5. #5
    Ik weet niet hoe TCSVDocument in elkaar zit - maar je zou zelf een objectlist kunnen maken met records er in, en deze vullen door een parser/explode functie. Een goede explode functie kan enorm snel zijn - en ik weet niet de csv bestanden zijn per regel gescheiden met comma? Dan kan je gewoon per regel een record maken en vullen met de data.

  6. #6
    Hoe langzaam is het?
    1+1=b

  7. #7
    Hobby programmeur
    Join Date
    Jul 2007
    Location
    De Bilt
    Posts
    262
    De csv bestanden die ik gebruik zijn alleen gescheiden met een puntkomma.
    Een csv bestand van 425kB met als inhoud 10001 rijen en 6 kolommen, duurt nu met TCSVDocument ongeveer 800ms om te laden vanaf de schijf. Bij 3 van dit soort bestanden zit ik dan al gauw op een totaal van 2,4 sec. voordat alles geladen is.

    De reden dat ik voor TCSVDocument had gekozen is omdat deze makkelijk en flexibel in gebruik is. Met flexibel bedoel ik dat het programma om moet kunnen gaan met csv bestanden waarbij de kolom volgorde of het kolom aantal verschillend kan zijn. Stel dat er later aan een csv bestand extra kolommen worden toegevoegd. Dan geeft dat denk ik in een objectlist problemen omdat daar van tevoren een vast bereik is ingesteld. Of is deze ook dynamisch te maken?

  8. #8
    Quote Originally Posted by sandertje View Post
    De csv bestanden die ik gebruik zijn alleen gescheiden met een puntkomma.
    Een csv bestand van 425kB met als inhoud 10001 rijen en 6 kolommen, duurt nu met TCSVDocument ongeveer 800ms om te laden vanaf de schijf. Bij 3 van dit soort bestanden zit ik dan al gauw op een totaal van 2,4 sec. voordat alles geladen is.
    Ieks.. even gekeken naar de source van TCSVDocument maar ik zie dat die in LoadFromFile gewoon gebruik maakt van TFileStream en dan karakter voor karakter leest. ZONDER BUFFER.

    Dus als je meer snelheid wilt lijkt het me handiger deze bestanden eerst in memory (TMemoryStream) te lezen en dan TCSVDocument.LoadFromStream() te gebruiken om de memory-stream te verwerken. Dat zou je wel eens heel veel snelheidswinst op kunnen leveren.

  9. #9
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357

  10. #10
    Ah, ik wist niet dat je dat als bug kon bestempelen

    Er zullen wel meer componenten gebruik maken van de standaard TFileStream i.p.v. een gebufferd exemplaar.
    Soms denk ik dan dat het handiger zou zijn om de standaard TFileStream aan te passen (zonder de compatibiliteit in gevaar te brengen).

  11. #11
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Tis een feature enhancement. Strict genomen had ik dat moeten aangeven.

    Maar het is snel gedaan. Ik ken de FPC buffered filestream echter niet zo goed, dus ik hoop dat Michael er zo naar kijkt. Als het in principe kan en er dan meer gevallen zijn, laat het me weten of file bugs, dan handel ik het af.

  12. #12
    Hobby programmeur
    Join Date
    Jul 2007
    Location
    De Bilt
    Posts
    262
    Quote Originally Posted by rvk View Post
    Ieks.. even gekeken naar de source van TCSVDocument maar ik zie dat die in LoadFromFile gewoon gebruik maakt van TFileStream en dan karakter voor karakter leest. ZONDER BUFFER.

    Dus als je meer snelheid wilt lijkt het me handiger deze bestanden eerst in memory (TMemoryStream) te lezen en dan TCSVDocument.LoadFromStream() te gebruiken om de memory-stream te verwerken. Dat zou je wel eens heel veel snelheidswinst op kunnen leveren.
    Ik ben niet bekend met TMemoryStream en heb daarom maar wat geprobeerd. De LoadFromFile in TCSVDocument heb ik aangepast naar het onderstaande. Dit lijkt overigens veel beter te werken want nu is het bestand in slechts 47ms geladen! Maar gebruik ik TMemoryStream nu wel op een correcte manier?

    Delphi Code:
    1. procedure TCSVDocument.LoadFromFile(const AFilename: String);
    2. var
    3.   MemoryStream: TMemoryStream;
    4. begin
    5.   MemoryStream := TMemoryStream.Create;
    6.   try
    7.     MemoryStream.LoadFromFile(AFilename);
    8.     LoadFromStream(MemoryStream);
    9.   finally
    10.     MemoryStream.Free;
    11.   end;
    12. end;

  13. #13
    Yep, dat gaat goed.

    Je hoeft in principe niet eens de source van TCSVDocumebt zelf te veranderen want je kan hier ook een functie of zelfs een class-helper van maken. Maar de code die je laat zien is correct en laat zien dat eerst de file in geheugen lezen voor het verwerken veel sneller gaat.

  14. #14
    In plaats van TMemoryStream zou je het dan ook met zo'n buffered TFileStream kunnen proberen. Misschien haal je dan dezelfde snelheid, en dan staat niet de hele file dubbel in het geheugen (al is dat maar voor even).
    1+1=b

  15. #15
    Quote Originally Posted by GolezTrol View Post
    In plaats van TMemoryStream zou je het dan ook met zo'n buffered TFileStream kunnen proberen. Misschien haal je dan dezelfde snelheid, en dan staat niet de hele file dubbel in het geheugen (al is dat maar voor even).
    Is er eigenlijk een buffered TFileStream in FPC ????
    Ik kon alleen een TBufStream vinden (wat een soort extensie op TStream is waar je nog wel eerst een TFileStream aan moet maken). Maar ik heb geen TBufferedFileStream gevonden (hoewel ik wel weet dat er custom versies zijn zoals in fpSpreadsheet).

    Even snel in elkaar gezet 3 methodes:
    Methode 1 is gewoon zelf in je code een TMemoryStream maken die de file inleest en dan TCSVDocument.LoadFromStream gebruiken.
    Methode 2 is een class helper met LoadFromFileWithMemorySupport() die TMemoryStream gebruikt.
    Methode 3 is een class helper met LoadFromFileWithBufferSupport() die TBufStream gebruikt.

    (Je ziet in ieder geval hier dat een class-helper veel makkelijker werkt dan de originele source aanpassen.)

    Delphi Code:
    1. uses CSVDocument, BufStream;
    2.  
    3. type
    4.   TMyCSVDocument = class helper for TCSVDocument
    5.     procedure LoadFromFileWithMemorySupport(const AFilename: string);
    6.     procedure LoadFromFileWithBufferSupport(const AFilename: string);
    7.   end;
    8.  
    9. procedure TMyCSVDocument.LoadFromFileWithMemorySupport(const AFilename: string);
    10. var
    11.   MemoryStream: TMemoryStream;
    12. begin
    13.   MemoryStream := TMemoryStream.Create;
    14.   try
    15.     MemoryStream.LoadFromFile(AFilename);
    16.     Self.LoadFromStream(MemoryStream);
    17.   finally
    18.     MemoryStream.Free;
    19.   end;
    20. end;
    21.  
    22. procedure TMyCSVDocument.LoadFromFileWithBufferSupport(const AFilename: string);
    23. var
    24.   BufStream: TBufStream;
    25.   FileStream: TFileStream;
    26. begin
    27.   FileStream := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone);
    28.   BufStream := TBufStream.Create(FileStream);
    29.   try
    30.     Self.LoadFromStream(BufStream);
    31.   finally
    32.     BufStream.Free;
    33.     FileStream.Free;
    34.   end;
    35. end;
    36.  
    37. procedure TForm1.FormCreate(Sender: TObject);
    38. const
    39.   AFilename = 'c:\temp\test.csv';
    40. var
    41.   CSV: TCSVDocument;
    42.   MemoryStream: TMemoryStream;
    43. begin
    44.  
    45.   // method 1
    46.   CSV := TCSVDocument.Create;
    47.   MemoryStream := TMemoryStream.Create;
    48.   try
    49.     MemoryStream.LoadFromFile(AFilename);
    50.     CSV.LoadFromStream(MemoryStream);
    51.     // DoSomethingWith CSV
    52.   finally
    53.     MemoryStream.Free;
    54.     CSV.Free;
    55.   end;
    56.  
    57.   // method 2
    58.   CSV := TCSVDocument.Create;
    59.   try
    60.     CSV.LoadFromFileWithMemorySupport(AFilename);
    61.     // DoSomethingWith CSV
    62.   finally
    63.     CSV.Free;
    64.   end;
    65.  
    66.   // method 3
    67.   CSV := TCSVDocument.Create;
    68.   try
    69.     CSV.LoadFromFileWithBufferSupport(AFilename);
    70.     // DoSomethingWith CSV
    71.   finally
    72.     CSV.Free;
    73.   end;
    74.  
    75. end;

Page 1 of 2 1 2 LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •