Autor Beitrag
cbs
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 207
Erhaltene Danke: 1



BeitragVerfasst: Sa 15.12.07 13:43 
Hallo,

Ich habe eine Datei die ein paar Millionen Werte enthält (kann stark varieren). Diese Werte möchte ich, um sie auszuwerten und weiterzuverabrbeiten, in genausoviele Objekte laden.

Das ist soweit kein Problem, dauert aber mitunter ein paar Minuten.

Im wesentlichen handelt es sich um folgende Struktur:

-Datum (8 Byte)
-Ganzzahl (1 Byte)
-Ganzzahl (4 Byte)
-Ganzzahl (4 Byte)

Bisher habe ich die TFileStream Klasse und die Methode ReadBuffer verwendet um die Werte zu lesen. Inszwischen kopiere ich die gesamte Datei mit CopyFrom in ein TMemoryStream Objekt bevor ich die Werte einlese, was einen Geschwindigkeitsvorteil bringt.

Meine Frage ist nun, ob ich das Einlesen irgendwie noch weiter beschleunigen kann. Irgendwie puffern, cachen oder was auch immer.

Ich opfere gern Arbeitsspeicher um die Geschwindigkeit zu erhöhen.

Danke schon mal!

mfg

cbs
jakobwenzel
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1889
Erhaltene Danke: 1

XP home, ubuntu
BDS 2006 Prof
BeitragVerfasst: Sa 15.12.07 14:11 
Glaskugel:

Der Fehler liegt in Zeile 666 der Unit blubb.pas. Wenns die nicht gibt, ist dein Delphi kaputt und muss neu installiert werden. :wink:

Ok, die Glaskugel ist immer noch kaputt...
Ohne deinen Code zu sehen, kann dir leider niemand helfen.

_________________
I thought what I'd do was, I'd pretend I was one of those deaf-mutes.
cbs Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 207
Erhaltene Danke: 1



BeitragVerfasst: Sa 15.12.07 18:42 
Ok verstanden.

Hier der relevante Code. Ich habs eben fix neu geschrieben damit es übersichtlicher ist. Habs nicht getestet aber müsste so funktionieren.

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

  TWert = class(TObject)
    Datum: TDateTime;
    Flags: Byte;
    Anzahl: LongInt;
    Uebereinstimmungen: LongInt;
  end;

var
  Form1: TForm1;
  daten: TList;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  streamM: TMemoryStream;
  streamF: TFileStream;
  cnt, cntr: Integer;
  d: TDateTime;
  b: Byte;
  i1, i2: Integer;
  wert: TWert;
begin
  streamM:= TMemoryStream.Create;
  streamF:= TFileStream.Create('D:\testdaten.data', fmOpenRead or fmShareDenyWrite);
  if Assigned(daten) = false then
  begin
    daten:= TList.Create;
  end else begin
    daten.Clear;
  end;
  try
    // Datei in Arbeitsspeicher laden
    streamM.CopyFrom(streamF, streamF.Size);

    // Anzahl Werte ermitteln
    cnt:= Trunc((8 + 1 + 4 + 4) / streamM.Size);

    // Werte einlesen
    streamM.Position:= 0;
    for cntr := 0 to cnt - 1 do
    begin
      streamM.ReadBuffer(d, 8);
      streamM.ReadBuffer(b, 1);
      streamM.ReadBuffer(i1, 4);
      streamM.ReadBuffer(i2, 4);
      wert:= TWert.Create;
      wert.Datum:= d;
      wert.Flags:= b;
      wert.Anzahl:= i1;
      wert.Uebereinstimmungen:= i2;
      daten.Add(wert);
    end;

  finally
    streamM.Free;
    streamF.Free;
  end;
end;

end.


Auf die Art lese ich diese und andere Dateien ein. Wie gesagt sind es teilweise mehrere Millionen Einträge. Um Arbeitsspeicher mache ich mir keine Sorgen, solange es nicht in die GB geht :wink: . Mir gehts primär um die Funktion und da möchte ich nicht bei jeder Datei Minuten lang warten bis der Einlese-Prozess abgeschlossen ist.

Drum: Gibt es eine Möglichkeit wie ich das ganze Beschleunigen kann?

Danke euch!

mfg

cbs
Allesquarks
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 510

Win XP Prof
Delphi 7 E
BeitragVerfasst: Sa 15.12.07 18:58 
Also so überschlagsweise solltest du pro Datensatz etwa 20 Byte verbrauchen und das 1 Million mal. Macht 20 MB total moderat ungefähr 1 sekunde zum einlesen von der Platte.

Aber ich denke es ist keine gute Idee für jeden Datensatz eine Instanz anzulegen. Denn die werden alle dynamisch erzeugt und alle einzeln. Das heißt du forderst ne Million mal an. Erstens kann das zu erheblicher fragmentierung führen (das ist dann später beim Zugriff auch langsamer) aber Speicher anfordern ist sowieso langsam. und Objekte erzeugen ebenfalls nochmal. Ich würde dir empfehlen deine "Klasse" in der du kein objektorientiertes Konzept benutzt zu einem record zu machen und ein array of Myrecord zu benutzen, was du einmal!! am Anfang auf die richtige Länge setzt.
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1654
Erhaltene Danke: 244

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: So 16.12.07 12:59 
Hallo,

dieses langsame Verhalten liegt aber an delphi.
Freepascal 2.2.0 ist erheblich schneller:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
(als "Test.dpr" gespeichert)
program Test;

{$APPTYPE console}
uses sysutils,classes;
const
  MaxZahl = 500000;
  MeineDatei='C:\MeineDaten.data';


type

  TWertRec = packed record
               Datum: TDateTime;
               Flags: Byte;
               Anzahl: LongInt;
               Uebereinstimmungen: LongInt;
             end;

  TWert = class(TObject)
    Datum: TDateTime;
    Flags: Byte;
    Anzahl: LongInt;
    Uebereinstimmungen: LongInt;
  end;


var
  t1,t0: TDAteTime;
    WerteFeld : array of TWertRec;

  daten: TList;

procedure Erzeugen;
var
  i : integer;
  Wert: tWertRec;
  WerteDatei : file of tWertRec;

begin
  AssignFile(WerteDatei,MeineDatei);
  rewrite(WerteDatei);
  randomize;
  For i := 1 to MaxZAhl do
    begin
    with wert do
      begin
      Datum:= now;
      Flags:= random(256);
      Anzahl:= i;
      Uebereinstimmungen:= random(Anzahl+1);
      IF MAxZahl< 1024 then
        Writeln(I:10,FormatDateTime(' DD.MM.YYYY hh:mm:ss.zzz',Datum),Flags:4,Anzahl:12,Uebereinstimmungen:12)
      else
        IF (I And $ffFF) = 0 then
         Writeln(I:10,FormatDateTime(' DD.MM.YYYY hh:mm:ss.zzz',Datum),Flags:4,Anzahl:12,Uebereinstimmungen:12);

      end;
    write(WerteDatei,Wert);
    end;
  CloseFile(WerteDatei);
end;


procedure EinlesenArray;
Var
  cnt:integer;
  WerteDatei : file of tWertRec;
begin
  AssignFile(WerteDatei,MeineDatei);
  reset(WerteDatei);
  cnt :=filesize(WerteDatei);
  writeln('Anzahl Datensätze: ',cnt);
  setlength(WerteFeld,cnt);
  BlockRead(WerteDatei,WerteFeld[0],cnt);
  closefile(WerteDatei);
end;

procedure EinlesenObject;
var
  streamM: TMemoryStream;
  streamF: TFileStream;
  cnt, cntr: Integer;
  d: TDateTime;
  b: Byte;
  i1, i2: Integer;
  wert: TWert;
begin
  streamM:= TMemoryStream.Create;
  streamF:= TFileStream.Create(MeineDatei, fmOpenRead or fmShareDenyWrite);
  if Assigned(daten) = false then
  begin
    daten:= TList.Create;
  end else begin
    daten.Clear;
  end;
  try
    // Datei in Arbeitsspeicher laden
    streamM.CopyFrom(streamF, streamF.Size);


    // Anzahl Werte ermitteln
    cnt:= streamM.Size DIV (SizeOf (Twertrec));
    writeln('Anzahl Datensätze: ',cnt);


    // Werte einlesen
    streamM.Position:= 0;
    Daten.count:= cnt;
    for cntr := 0 to cnt - 1 do
      begin
      wert:= TWert.Create;
      with wert do
        begin
        streamM.ReadBuffer(datum,SizeOf(Datum));
        streamM.ReadBuffer(Flags, SizeOf(Flags));
        streamM.ReadBuffer(Anzahl, SizeOf(Anzahl));
        streamM.ReadBuffer(Uebereinstimmungen,SizeOf(Uebereinstimmungen));
        end;
      daten[cntr]:=wert;
      IF (cntr And $FFFF) = 0 then
         with Twert(daten[cntr]) do
           Writeln(cntr:10,FormatDateTime(' DD.MM.YYYY hh:mm:ss.zzz',Datum),Flags:4,Anzahl:12,Uebereinstimmungen:12);

    end;

    Writeln(Daten.count);
  finally
    streamM.Free;
    streamF.Free;
  end;
end;

begin
  //Erzeugen;
  T0:= now;
  EinlesenArray;
  T1:= now;
  //Platz wieder freigeben 
  setlength(WerteFeld,0);
  Writeln(FormatDateTime(' hh:mm:ss.zzz',T1-T0));

  T0:= now;
  EinlesenObject;
  T1:= now;
  Writeln(FormatDateTime(' hh:mm:ss.zzz',T1-T0));
  readln;
End.


Mit Delphi7(Nutzung von FastMM4 Ohne Unterschied)
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Anzahl Datensätze: 500000
 00:00:00.030
Anzahl Datensätze: 500000
         0 16.12.2007 11:28:06.797 186           1           1
     65536 16.12.2007 11:28:06.998 184       65537       38021
    131072 16.12.2007 11:28:07.208 182      131073       33876
    196608 16.12.2007 11:28:07.408 180      196609      184177
    262144 16.12.2007 11:28:07.619 178      262145      161241
    327680 16.12.2007 11:28:07.829 176      327681       96140
    393216 16.12.2007 11:28:08.029 174      393217      382093
    458752 16.12.2007 11:28:08.239 172      458753      298201
500000
 00:00:01.652

Mit Freepascal
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
■ Free Pascal IDE Version 1.0.10 [2007/09/09]
■ Compiler Version 2.2.0
■ GBD Version GDB 6.2.1
■ Cygwin "C:\FPC\2.1.4\bin\i386-win32\cygwin1.dll" version 1005.18.0.0
Running "c:\test.exe "
Anzahl Datensätze: 500000
 00:00:00.060
Anzahl Datensätze: 500000
         0 16.12.2007 11:28:06.797 186           1           1
     65536 16.12.2007 11:28:06.998 184       65537       38021
    131072 16.12.2007 11:28:07.208 182      131073       33876
    196608 16.12.2007 11:28:07.408 180      196609      184177
    262144 16.12.2007 11:28:07.619 178      262145      161241
    327680 16.12.2007 11:28:07.829 176      327681       96140
    393216 16.12.2007 11:28:08.029 174      393217      382093
    458752 16.12.2007 11:28:08.239 172      458753      298201
500000
 00:00:00.320

Mit 5 Mio Datensätzen nur Freepascal, Delphi7 dauerte mir zu lange...
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
Running "c:\test.exe "
Anzahl Datensätze: 5000000
 00:00:00.450
Anzahl Datensätze: 5000000
         0 16.12.2007 11:35:41.742  88           1           1
   1048576 16.12.2007 11:35:45.387 131     1048577      953161
   2097152 16.12.2007 11:35:49.012 183     2097153      793167
   3145728 16.12.2007 11:35:52.627 225     3145729     2012977
   4194304 16.12.2007 11:35:56.252  35     4194305      451753
5000000
 00:00:02.464


Das ist ja ein merkwürdig großer Unterschied

Gruß Horst
hathor
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: So 16.12.07 13:03 
RAMDISK
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1654
Erhaltene Danke: 244

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: So 16.12.07 13:54 
Hallo,

RAMDISK , Speicher ist durch nichts zu ersetzen.
Ich habe bei mir festgestellt, das die Auslagerungsdatei bei 5 Mio Datensätzen (85 MB) beansprucht wird( 512 MB Hauptspeicher WinXP , Notebook mit Sparmodi, das heisst, dass nach ein paar Zehntelsekunden der Takt von 600Mhz auf 1700 erhöht wird))

Deshalb habe ich die Datenobjektliste ohne File- und memory-stream eingelesen:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
procedure EinlesenObject;
var
  cnt, cntr: Integer;
  WertRec: TWertrec;
  WerteDatei : file of tWertRec;

begin
  Assign(WerteDatei,MeineDatei);
  reset(WerteDatei);
  if Assigned(daten) = false then
  begin
    daten:= TList.Create;
  end else begin
    daten.Clear;
  end;
  try
    // Anzahl Werte ermitteln
    cnt:= fileSize(WerteDatei);
    writeln('Anzahl Datens„tze: ',cnt);
    readln;
    // Werte einlesen
    Daten.count:= cnt;
    for cntr := 0 to cnt - 1 do
      begin
      wert:= TWert.Create;
      Read(WerteDatei,WertRec);
      with wert do
        begin
        datum:= WertRec.Datum;
        Flags:= WertRec.Flags;
        Anzahl:= WertRec.Anzahl;
        Uebereinstimmungen:= WertRec.Uebereinstimmungen;
        end;
      daten[cntr]:=wert;
      IF (cntr And $FFFFF) = 0 then
         with Twert(daten[cntr]) do
           Writeln(cntr:10,FormatDateTime(' DD.MM.YYYY hh:mm:ss.zzz',Datum),Flags:4,Anzahl:12,Uebereinstimmungen:12);
    end;

    Writeln(Daten.count);
  finally
    closeFile(WerteDatei);
  end;
end;

delphi7:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
Anzahl Datensätze: 5000000
 00:00:00.791
Anzahl Datensätze: 5000000

         0 16.12.2007 11:35:41.742  88           1           1
   1048576 16.12.2007 11:35:45.387 131     1048577      953161
   2097152 16.12.2007 11:35:49.012 183     2097153      793167
   3145728 16.12.2007 11:35:52.627 225     3145729     2012977
   4194304 16.12.2007 11:35:56.252  35     4194305      451753
5000000
 00:00:06.960

Freepascal:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
■ Free Pascal IDE Version 1.0.10 [2007/09/09]
■ Compiler Version 2.2.0
■ GBD Version GDB 6.2.1
■ Cygwin "C:\FPC\2.1.4\bin\i386-win32\cygwin1.dll" version 1005.18.0.0
Running "c:\test.exe "
Anzahl Datensätze: 5000000
 00:00:00.370
Anzahl Datensätze: 5000000

         0 16.12.2007 11:35:41.742  88           1           1
   1048576 16.12.2007 11:35:45.387 131     1048577      953161
   2097152 16.12.2007 11:35:49.012 183     2097153      793167
   3145728 16.12.2007 11:35:52.627 225     3145729     2012977
   4194304 16.12.2007 11:35:56.252  35     4194305      451753
5000000
 00:00:05.538


Delphi wird nun wesentlich schneller.
Es liegt also an der zu späten Freigabe des FileStreams (hätte nach dem Kopieren in den Memorystream freigegeben werden können) und des Speicherplatzes des Memorystreams.
Liest man die Daten mit read statt mit BlockRead , dauert das Einlesen Array ~4,6 Sekunden. Das Erstellen der 5 Mio Objekte in der Liste und kopieren von WertRec in Wert dauert nur ca eine Sekunde.
Bei so wenig Daten pro Objekt ist der Overhead gewaltig. 178 Mb statt 85 Mb werden belegt.


Was soll's. Nutzung eines Feldes und laden mittels Blockread ist mit Abstand das schnellste und Resourcen sparend.

Gruß Horst
cbs Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 207
Erhaltene Danke: 1



BeitragVerfasst: Mi 19.12.07 12:12 
Ich danke euch, das hat mir sehr geholfen!

mfg

cbs