Entwickler-Ecke

Dateizugriff - INI-Dateien schneller schreiben


LonghornUser - So 23.05.10 18:07
Titel: INI-Dateien schneller schreiben
Hallo,

ich habe im Moment das Problem, dass das Schreiben von INI-Dateien schon bei einer Größe von ~1KB knapp 1 Sekunde dauert. Wenn ich das ganze auf einem langsameren Rechner (Notebook) mache, dauert es schon 5-6 Sekunden.

Ist das normal?? Gibt es eine Möglichkeit, diesen Vorgang zu beschleunigen (etwa ein anderes Format)?

Danke!!

Ciao LHUser


jaenicke - So 23.05.10 18:21

Da machst du wohl etwas falsch, aber ohne Code kann da niemand etwas dazu sagen. :nixweiss:

Ich hatte jedenfalls damals als ich noch INIs verwendet habe auch größere in wenigen Millisekunden eingelesen oder erstellt. Die Frage ist aber wie, denn es gibt die API-Funktionen, die Delphikapselung dazu (beides deprecated, also veraltet markiert, da nur für Kompatibilität zu Windows 3.x noch vorhanden) und eigene Möglichkeiten.

Zum Vergleich: Das Einlesen von Registrydateien, die im Grunde ja auch nur INIs sind, schaffe ich mit ca. 30-90 MiB/s, je nach Festplattengeschwindigkeit...

// EDIT:
Ach ja: wenn dir Geschwindigkeit wichtig ist, warum benutzt du dann INI-Dateien?!?


Gausi - So 23.05.10 19:17

Ini-Dateien sind nicht zur Datenspeicherung da, aber ich habe auch einige im Bereich von einigen kb. Nutzt du TIniFile oder TMemIniFile? Letzteres arbeitet erstmal nur im Speicher, auf Platte muss explizit durch MyIni.UpdateFile geschrieben werden.

Hatte das Problem mal, als ich noch mit einer Diskette gearbeitet habe. Mit IniFile war das Laufwerk ne Minute am rödeln, mit TMemIniFile ging das recht flott. :D


hansa - So 23.05.10 19:32

Zitat:
Hatte das Problem mal, als ich noch mit einer Diskette gearbeitet habe. Mit IniFile war das Laufwerk ne Minute am rödeln, mit TMemIniFile ging das recht flott. :D


Dann hätteste mal einwandfreie Diskette nehmen sollen und keine mit zig Lesefehlern. :mrgreen:


Gausi - So 23.05.10 20:01

Lesen ging auch recht zügig, aber 200 mal Schreibzugriff auf ne Floppy war dann doch etwas doof. ;-)


LonghornUser - Mo 24.05.10 13:56

OK. Da ich auch denke, dass ich nur einen Fehler drin habe, der die Sache lang macht, hier mal der entsprechende (gekürzte) Quelltext:

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:
procedure TForm1.Save(Filename:String);
var 
  ini:TIniFile;
  i,z,q,j,l:Integer;
  s1:string;
begin

// Löschen einer vorhandenen INI-Datei
    if FileExists(Filename) then
      DeleteFile(Filename);
// Daten werden in Konfig.ini geschrieben (Andwendungsdaten des aktiven Users)
    ini := TIniFile.Create(Filename);
    i := 0;
    z := 0;
    for i := 0 to ListView1.GetCount-1 do begin // In einem ListView sind die Daten organisiert
      q := 1;
      s1 := '';
      INC(z);
      ini.WriteString('Section'+inttostr(i+1),'Section',
        ListView1.Items[i].Caption);
      ini.WriteString('Section'+inttostr(i+1),'Key1',
        ListView1.Items[i].SubItems.Strings[0]);

// Der folgende Teil kommt für bestimmte Elemente abgewandelt noch 6-mal

      if ListView1.Items[i].SubItems.Strings[0] = someString1 then begin
        if  (Array1[i] <> '')
        and (Array1[i] <> someString2) then
          ini.WriteString('Section'+inttostr(i+1),'Key2',Array1[i]) else
          ini.WriteString('Section'+inttostr(i+1),'Key2',SomeString2);
        ini.WriteString('Section'+inttostr(i+1),'Key3',Array2[i]);
        ini.WriteString('Section'+inttostr(i+1),'Key4',Array3[i]);
      end;

// (bis hier)

// Dieser Part teilt einen großen Text, der in einem Array steht in 100-Zeichen-Teile auf und schreibt ihn in die INI
      if ListView1.Items[i].SubItems.Strings[0] = someString3 then
        if (Array3[i]<>''and (Array3[i]<>someString4) then begin
        for j := 1 to length(Array3[i]) do begin
          if j MOD 100 = 0 then begin
              INC(q);
              s1 := '';
              ini.WriteString('Section'+inttostr(i+1),'Key5_'+inttostr(q),s1);
          end;
          if Array3[i][j] <> '' then begin
            ini.WriteString('Section'+inttostr(i+1),'Count',inttostr(q));
            s1 := s1 + Array3[i][j];
            ini.WriteString('Section'+inttostr(i+1),'Key5_'+inttostr(q),s1);
          end;
        end;
        if q = 1 then begin
          ini.WriteString('Section'+inttostr(i+1),'Key5_1',s1);
          ini.WriteString('Section'+inttostr(i+1),'Count','1');
        end;
      end else
        ini.WriteString('Section'+inttostr(i+1),'Key5',someString4);

    end;
  ini.WriteInteger('SectionCount','Count',z);
  ini.Free;
end;


jaenicke - Mo 24.05.10 14:39

Der größte Fehler, der auf Anhieb klar ist, ist, dass die Daten offenbar nur in der TListView liegen. Und der Zugriff auf die visuellen Komponenten ist nun einmal langsam. Die Daten gehören dort nur angezeigt, mehr nicht...

Wenn du die Daten im Hintergrund in einer geeigneten Datenstruktur hältst, dann ist der Zugriff auch entsprechend schneller. Ansonsten ist die größte Bremse wohl die Benutzung von INIs selbst. Dass das soo langsam ist, hätte ich trotzdem nicht gedacht.


LonghornUser - Mo 24.05.10 16:16

Ich wollte ja mal BigIni ausprobieren, aber unter Delphi 2010 bekomme ich das ganze nicht zum Laufen (es kommen Fehler beim Kompilieren von BigIni.pas) :( Ob das was helfen würde?

Ich kann irgendwie nicht glauben, dass nur die Benutzung des ListViews soviel Zeit kosten soll (im Sekundenbereich) :?


Lannes - Mo 24.05.10 16:29

Hallo,

wenn ich das richtig sehe schreibt der Code bei jedem der 100 Zeichen immer wieder das gleiche in die ini:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
for j := 1 to length(Array3[i]) do
  begin
  if j MOD 100 = 0 then
    begin
    INC(q);
    s1 := '';
    ini.WriteString('Section'+inttostr(i+1),'Key5_'+inttostr(q),s1);
    end;
  if Array3[i][j] <> '' then
    begin
    ini.WriteString('Section'+inttostr(i+1),'Count',inttostr(q));    //<- !!!
    s1 := s1 + Array3[i][j];
    ini.WriteString('Section'+inttostr(i+1),'Key5_'+inttostr(q),s1); //<- !!!
    end;
  end;


LonghornUser - Mo 24.05.10 16:48

Nö, das macht er eigentlich nicht. Ist vielleicht etwas kryptisch geschrieben, aber tut seinen Zweck :)

Aber vielleicht gibt es eine bessere Variante, einen Text in 100-Zeichen-Abschnitte zu zerteilen? Es hat sich nämlich gezeigt, dass genau dieser Abschnitt Probleme macht (der MOD-Abschnitt). Irgendetwas ist da ziemlich langsam.


jaenicke - Mo 24.05.10 17:12

So, ich habe es einmal getestet. Der Flaschenhals ist wie ich auch hauptsächlich dachte die INI-Nutzung. Beim Lesen aus der ListView ist diese (anders als ein Memo!!) nicht langsamer (das hätte ich nicht gedacht).

Zum Vergleich: Die normale Speicherung in einem eigenen Format dauert unter 15 Millisekunden für 1000 Einträge, bei einer INI sind es ca. 50 Sekunden...


elundril - Mo 24.05.10 17:21

Vielleicht hilft das die FastIni-Unit [http://www.delphi-forum.de/topic_FastIniFiles+32_75874.html] von Silas das ganze etwas zu beschleunigen.

lg elundril


LonghornUser - Mo 24.05.10 17:25

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
So, ich habe es einmal getestet. Der Flaschenhals ist wie ich auch hauptsächlich dachte die INI-Nutzung. Beim Lesen aus der ListView ist diese (anders als ein Memo!!) nicht langsamer (das hätte ich nicht gedacht).

Zum Vergleich: Die normale Speicherung in einem eigenen Format dauert unter 15 Millisekunden für 1000 Einträge, bei einer INI sind es ca. 50 Sekunden...


Danke für deine Mühe!! Das hätte ich nicht gedacht. Dabei speichere ich ja auch nicht wirklich viel. Sind wie gesagt vielleicht 1KB.

user profile iconelundril hat folgendes geschrieben Zum zitierten Posting springen:
Vielleicht hilft das die FastIni-Unit [http://www.delphi-forum.de/topic_FastIniFiles+32_75874.html] von Silas das ganze etwas zu beschleunigen.

lg elundril


Das schaue ich mir nachher mal an, danke für den Tipp!


jaenicke - Mo 24.05.10 17:35

Ich habe es einmal mit TMemIniFile und TFastIniFile getestet. Die beiden sind nur etwa 7 bis 12 Mal langsamer als eine eigene Speicherung (100ms bis 150ms gegenüber 12-16ms). Die nehmen sich aber nicht viel, jedenfalls bei meinem Test waren die fast genauso schnell.


LonghornUser - Mo 24.05.10 18:10

Also kann ich praktisch auch das bereits in Delphi integrierte MemIni nehmen?

Edit: Ich habe es jetzt einfach mal mit TMemIniFile probiert und siehe da: Das Schreiben funktioniert wesentlich schneller.

Vielen Dank für eure Hilfe bis dahin!!!

Ist MemIniFile eigentlich auch beim Lesen schneller als IniFile?


jaenicke - Mo 24.05.10 18:33

Ich habe es nicht ausprobiert, aber ich denke ja, denn es basiert ja darauf, dass die Daten im Arbeitsspeicher gehalten werden.

Und wie gesagt: Mit einer eigenen Datenspeicherung hat man kleinere Datendateien, deutlich mehr Geschwindigkeit und einfacher ist es auch.


Lannes - Mo 24.05.10 22:50

Hallo,
user profile iconLonghornUser hat folgendes geschrieben Zum zitierten Posting springen:
Nö, das macht er eigentlich nicht.
dann gib mal zusätzlich die geschriebenen Ini-Werte in einem Memo aus.
Es ist richtig, der Code liefert im Ergebnis das was du willst, der letzte Schreibvorgang schreibt den Eintrag den du möchtest. Zuvor wird aber 100-mal der Ini-Eintrag überschrieben. Und da zwei Einträge geschrieben werden sind es 200 Schreibvorgänge.


LonghornUser - Di 25.05.10 13:00

Du hattest doch recht. Der Code (der unter uns gesagt mehrere Jahre alt ist ;)) war äußerst ineffizient.

Daher habe ich das ganze überarbeitet und dieser Code hier sollte mit deutlich weniger Schreibzugriffen auskommen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
for j := 0 to length(Array3[i]) div 100 do
          begin
            ini.WriteString('Section'+inttostr(i+1), 'Key5_'+inttostr(j+1), '');
            ini.WriteString('Section'+inttostr(i+1), 'Key5_'+inttostr(j+1),
              Copy(Array3[i], j*100+1100));
            ini.WriteString('Section'+inttostr(i+1), 'Count', inttostr(j+1));
          end;


Lannes - Di 25.05.10 18:57

user profile iconLonghornUser hat folgendes geschrieben Zum zitierten Posting springen:
Du hattest doch recht. Der Code (der unter uns gesagt mehrere Jahre alt ist ;)) war äußerst ineffizient.
ja, die Jugendsünden :wink:
Da sind immer noch überflüssige Schreibzugriffe und Berechnungen enthalten, teste mal den Code:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var Sec: String;
    Count: Integer;
begin
  Sec := 'Section' + IntToStr(Succ(i));
  Count := Succ(Length(Array3[i]) div 100));
  for j := 1 to Count do
    ini.WriteString(Sec, 'Key5_' + IntToStr(j), Copy(Array3[i], Pred(j)*100100));
  ini.WriteString(Sec, 'Count', IntToStr(Count);


LonghornUser - Di 25.05.10 19:08

Ja, das war eine ziemliche Jugendsünde ;)

Was genau sind denn Succ und Pred?


Lannes - Di 25.05.10 19:12

Succ = Nachfolger von einem ordinalen Datentyp
Pred = Vorgänger von einem ordinalen Datentyp