![]() |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Zitat:
Egal was Du in Deinem Programm machst: Es ist immer möglich, dass zwischen zwei Programmschritten, die immer korrekt verlaufen, irgendwann doch mal der Strom weg ist, ein Hardwaredefekt auftritt, der Anwender einen Prozess abschießt... Daher ja mein Vorschlag, immer wenn was wichtiges in der INI geändert wurde, UpdateFile aufzurufen. Oder halt eine Routine implementieren, die Dir mehrere Sicherungeskopieen der Ini-Datei erstellen. Dir scheint die Korrektheit und Speicherung der INI-Datei ja sehr wichtig zu sein. Beschreibe doch einfach mal kurz, warum, in welchem Umfeld... Eventuell wird es für uns verständlicher, Deine Sorgen zu teilen und eventuell einen anderen Lösungsansatz zur Problemlösung zu finden. Welche Datenmengen stehen in der INI-Datei? Könnte eine Embedded-Firebird-Datenbank oder eine SQLite-Datenbank... oder sowas helfen? Da sind die Daten in 'ner Transaktion per Commit weggeschrieben und der Stand ist dann beim nächsten Programmstart vorhanden, eine Datenbankdatei ist nicht bei 'nem misslungenen Update leer oder wird vor dem Schreiben gelöscht und dann neu erstellt. Unter Delphi 7 sieht UpdateFile so aus:
Delphi-Quellcode:
In einer Ableitung könnte man das ja dahingehend überschreiben, dass man nach dem SaveToFile noch prüft, ob die Datei auch wirklich erstellt wurde und nicht leer ist.
procedure TMemIniFile.UpdateFile;
var List: TStringList; begin List := TStringList.Create; try GetStrings(List); List.SaveToFile(FFileName); finally List.Free; end; end; Man könnte sie auch anschließend in eine zweite Stringliste laden und die beiden Stringlisten vergleichen...
Delphi-Quellcode:
(Das ist jetzt nur mal so "hingedaddelt" und nicht getestet.)
procedure TMemIniFile.UpdateFile;
var List: TStringList; List2: TStringList; begin List := TStringList.Create; List2 := TStringList.Create; try try // Hier eventuell die in früherem Post vorgeschlagene "Historisierung" älterer INI-Dateien einbauen... GetStrings(List); List.SaveToFile(FFileName); List2.LoadFromFile(FFileName); if List.Text <> List2.Text then begin MessageDLG('Abweichung zwischen vor und nach dem Speichern der INI-Datei.',mtError,[mbOk],0); end; except on e : Exception do begin // Irgendwelche Sicherungsmaßnahmen... MessageDLG(e.Message,mtError,[mbOk],0); end; end; finally List2.Free; List.Free; end; end; Schau Dir bitte in den Delphi-Quellen mal GetStrings von TMemIniFile an, könnte da (bedingt durch die verwendeten Daten) eventuell ein Fehler auftreten? |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Mein Vorschlag wäre:
Delphi-Quellcode:
unit System.IniFilesEx;
interface uses System.IniFiles, System.SysUtils; type TSafeMemIniFile = class( TMemIniFile ) private const ExtOldData = '.bak'; ExtOldEmpty = '.ebak'; private FSaveOnDestroy: Boolean; procedure RestoreFileState( const FileName: string ); procedure DeleteFileIfExists( const FileName: string ); function GetOldFileName: string; function GetEmptyFileName: string; protected property OldFileName : string read GetOldFileName; property EmptyFileName: string read GetEmptyFileName; public constructor Create( const FileName: string ); overload; constructor Create( const FileName: string; const Encoding: TEncoding ); overload; destructor Destroy; override; procedure UpdateFile; override; property SaveOnDestroy: Boolean read FSaveOnDestroy write FSaveOnDestroy default true; end; implementation uses System.Classes, System.IOUtils; { TSafeMemIniFile } constructor TSafeMemIniFile.Create( const FileName: string ); begin RestoreFileState( FileName ); inherited Create( FileName ); FSaveOnDestroy := true; end; constructor TSafeMemIniFile.Create( const FileName: string; const Encoding: TEncoding ); begin RestoreFileState( FileName ); inherited Create( FileName, Encoding ); FSaveOnDestroy := true; end; procedure TSafeMemIniFile.DeleteFileIfExists( const FileName: string ); begin if TFile.Exists( FileName ) then TFile.Delete( FileName ); end; destructor TSafeMemIniFile.Destroy; begin if SaveOnDestroy then UpdateFile( ); inherited; end; function TSafeMemIniFile.GetEmptyFileName: string; begin Result := FileName + ExtOldEmpty; end; function TSafeMemIniFile.GetOldFileName: string; begin Result := FileName + ExtOldData; end; procedure TSafeMemIniFile.RestoreFileState( const FileName: string ); begin if TFile.Exists( OldFileName ) or TFile.Exists( EmptyFileName ) then begin DeleteFileIfExists( FileName ); if TFile.Exists( OldFileName ) then TFile.Move( OldFileName, FileName ); DeleteFileIfExists( EmptyFileName ); end; end; procedure TSafeMemIniFile.UpdateFile; begin RestoreFileState( FileName ); if TFile.Exists( FileName ) then TFile.Move( FileName, OldFileName ) else TFile.WriteAllText( EmptyFileName, '' ); inherited; DeleteFileIfExists( OldFileName ); DeleteFileIfExists( EmptyFileName ); end; end. |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Ich habe jetzt mal die Geschwindigkeit von TMemIniFile beim Schreiben überprüft.
Getestet habe ich in verschiedenen Konstellationen, aber nachfolgend beispielhaft eine Variante mit Schreiben von 1000 Werten, die jeweils aus 30 zufälligen Zeichen bestehen.
Delphi-Quellcode:
var
ini : TMemIniFile; i : Integer; LogDateStart : TDateTime; begin LogDateStart := Now; ini := TMemIniFile.Create(txtFileName.Text); try for i := 0 to rbgValues.Tag-1 do ini.WriteString(txtSection.Text+IntToStr(i), txtIdent.Text+IntToStr(i), RandomPassword(30)); ini.UpdateFile; finally ini.Free; end; memLog.Lines.Add('MEM (' + IntToStr(rbgValues.Tag) +'): '+FormatDateTime('ss:zzz',LogDateStart-Now)+'sec');
Code:
Dass noch andere Faktoren hineinspielen, die sich auf die Dauer bei der Genauigkeit auswirken, ist natürlich klar. Aber dass der Speichervorgang ohne Ini ein vielfaches länger dauert als mit vorhandener Datei, ist meiner Meinung nach doch sehr bezeichnend.
-> In der Ausgangslage ist keine Ini-Datei vorhanden
MEM (1000): 00:163sec MEM (1000): 00:035sec MEM (1000): 00:024sec MEM (1000): 00:033sec -> Hier Ini-Datei gelöscht MEM (1000): 00:161sec MEM (1000): 00:023sec MEM (1000): 00:015sec MEM (1000): 00:021sec |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Du hast jetzt erfolgreich gemessen, wie lange das Dateisystem benötigt den Dateinamen einzutragen und den Speicherplatz zu finden.
War das nicht auch zu erwarten? Und je voller die Platte, desto länger dauert das (mit dem Speicherplatz finden) |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Zitat:
![]() Frag also nicht mich. ;) |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Warum implementierst du nicht einfach mal Uwes Vorschlag und guckst, was passiert?
Wie helfen dir diese Performancemessungen bei der Lösung deines Problems? Murphy kann immer an einer Stelle deines Programmes zu schlagen, wo es schief geht. Mitten im Umbennnen der Ini-Datei, Strom weg, Bämm. Wobei Murphy das sehr gut timen müsse. Das umbenennen dauert ja nur Millisekunden. |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Zitat:
Das reine UpdateFile (1000 Sections mit je einem Eintrag -> ca. 30KB) dauert auf meinem System ca. 6,8 ms(!) - egal, ob die Datei vorher existiert oder nicht. Mit meinem Vorschlag erhöht sich das auf ca. 7.3 ms. Erhöht man die Zahl der Sections auf 10000 (ca. 300KB) bekomme ich Zeiten von 7.4 und 7.8 ms, wobei man hier mit umgebungsbedingten Toleranzen von +/- 0.5 ms rechnen muss.
Delphi-Quellcode:
program Project53;
{$APPTYPE CONSOLE} uses System.SysUtils, System.Diagnostics, System.IniFiles, System.IOUtils; const cFileName = 'C:\TEMP\MyIni.ini'; cLineCount = 1000; type TMyInifile = class(TMemIniFile) private FBackupName: string; public constructor Create(const AFileName: string); procedure UpdateFile; override; property BackupName: string read FBackupName; end; constructor TMyInifile.Create(const AFileName: string); begin FBackupName := TPath.ChangeExtension(AFileName, '.bak'); if TFile.Exists(BackupName) then begin { eventuell noch weitere Überprüfungen } if TFile.Exists(AFileName) then begin TFile.Delete(BackupName); end else begin TFile.Move(BackupName, AFileName); end; end; inherited; end; procedure TMyInifile.UpdateFile; begin if TFile.Exists(FileName) then begin if TFile.Exists(BackupName) then begin TFile.Delete(BackupName); end; TFile.Move(FileName, BackupName); end; inherited; end; type TMemIniFileHelper = class helper for TMemIniFile public procedure Test; end; procedure TMemIniFileHelper.Test; var deltaTime: Int64; i: Integer; ini: TMemIniFile; sw: TStopWatch; N: Integer; S: string; begin for I := 0 to cLineCount - 1 do begin S := IntToStr(i); WriteString('Section' + S, 'Ident' + S, S); end; sw := TStopWatch.StartNew; for N := 1 to 100 do begin UpdateFile; end; sw.Stop; Writeln('Runtime [', ClassName, ']: ', sw.ElapsedMilliseconds/100:1:3, ' ms'); end; procedure Main; var ini: TMemIniFile; begin ini := TMemIniFile.Create(cFileName); try ini.Test; finally ini.Free; end; ini := TMyIniFile.Create(cFileName); try ini.Test; finally ini.Free; end; end; begin try Main; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; end. |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Hier nochmal eine Version, die auch die Alternative mit dem zweifachen Speichern berücksichtigt. Die Laufzeiten sind erwartungsgemäß etwa doppelt so lang wie bei einem originalen TMemIniFile.
Delphi-Quellcode:
program Project53;
{$APPTYPE CONSOLE} uses System.SysUtils, System.Diagnostics, System.IniFiles, System.IOUtils, System.Classes; const cFileName = 'C:\TEMP\MyIni.ini'; cLineCount = 1000; type TSaveMemIniFile = class(TMemIniFile) private FBackupName: string; public constructor Create(const AFileName: string); property BackupName: string read FBackupName; end; TSaveMemIniFileRename = class(TSaveMemIniFile) private public procedure UpdateFile; override; end; TSaveMyIniFileDoubleWrite = class(TSaveMemIniFile) public procedure UpdateFile; override; end; constructor TSaveMemIniFile.Create(const AFileName: string); begin FBackupName := TPath.ChangeExtension(AFileName, '.bak'); if TFile.Exists(BackupName) then begin { eventuell noch weitere Überprüfungen } if TFile.Exists(AFileName) then begin TFile.Delete(BackupName); end else begin TFile.Move(BackupName, AFileName); end; end; inherited; end; procedure TSaveMemIniFileRename.UpdateFile; begin if TFile.Exists(FileName) then begin if TFile.Exists(BackupName) then begin TFile.Delete(BackupName); end; TFile.Move(FileName, BackupName); end; inherited; { Hier könnte man die INI-Datei überprüfen und im Erfolgsfall die Backup-Datei löschen, aber das würde eh im Create oder nächsten UpdateFile gemacht. } end; procedure TSaveMyIniFileDoubleWrite.UpdateFile; var List: TStringList; begin List := TStringList.Create; try GetStrings(List); List.SaveToFile(BackupName, Encoding); finally List.Free; end; inherited; end; type TMemIniFileHelper = class helper for TMemIniFile public procedure Test; end; procedure TMemIniFileHelper.Test; var deltaTime: Int64; i: Integer; ini: TMemIniFile; sw: TStopWatch; N: Integer; S: string; begin for I := 0 to cLineCount - 1 do begin S := IntToStr(i); WriteString('Section' + S, 'Ident' + S, S); end; sw := TStopWatch.StartNew; for N := 1 to 100 do begin UpdateFile; end; sw.Stop; Writeln('Runtime [', ClassName, ']: ', sw.ElapsedMilliseconds/100:1:3, ' ms'); end; procedure Main; var ini: TMemIniFile; begin ini := TMemIniFile.Create(cFileName); try ini.Test; finally ini.Free; end; ini := TSaveMemIniFileRename.Create(cFileName); try ini.Test; finally ini.Free; end; ini := TSaveMyIniFileDoubleWrite.Create(cFileName); try ini.Test; finally ini.Free; end; end; begin try Main; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; end. |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Jemand auch an das FLUSH beim Schreiben gedacht?
Tipp: TStringList -> TFileStream -> FlushFileBuffers (man könnte auch direkt NonBuffered schreiben, aber so geht es einfacher) |
AW: Ini-Inhalt geht in sehr seltenen Fällen verloren (TMemIniFile)
Ehrlich gesagt finde ich die Diskussion über die Geschwindigkeit langsam absurd:
Wenn es um's Messen der Schreibgeschwindigkeit geht, dann bitte auch nur das Schreiben messen:
Delphi-Quellcode:
Hieraus kann ich nicht entnehmen, dass hier irgendwas ein vielfaches von was anderem dauert:
var
ini : TMemIniFile; i : Integer; dtStart : TDateTime; dtEnde : TDateTime; begin ini := TMemIniFile.Create(txtFileName.Text); try for i := 0 to rbgValues.Tag-1 do ini.WriteString(txtSection.Text+IntToStr(i), txtIdent.Text+IntToStr(i), RandomPassword(30)); dtStart := Now; ini.UpdateFile; dtEnde := Now; finally ini.Free; end; // Hier bitte die Startzeit von der Endezeit abziehen und nicht andersherum. // Sonst bekommt man eine negative Zeit, der man einfach das Vorzeichen nimmt und dann suggeriert, man habe eine positive Zeitdifferenz ;-) memLog.Lines.Add('MEM (' + IntToStr(rbgValues.Tag) +'): '+FormatDateTime('ss.zzz',dtEnde - dtStart)+'sec');
Code:
Beim Ersten gibt es eine Durchschnittszeit von 63,75 ms, beim Zweiten sind es 55 ms.
-> In der Ausgangslage ist keine Ini-Datei vorhanden
MEM (1000): 00:163sec MEM (1000): 00:035sec MEM (1000): 00:024sec MEM (1000): 00:033sec -> Hier Ini-Datei gelöscht MEM (1000): 00:161sec MEM (1000): 00:023sec MEM (1000): 00:015sec MEM (1000): 00:021sec Auf olympische Wettkämpfe übertragen ergibt das (gerundet) einen Unterschied von 9 hunderstel Sekunden zwischen dem Sieger und dem Zweitplazierten. Mir scheint, dass dann, wenn eine INI-Datei vorhanden ist, alles "wesentlich" schneller zu sein, als wenn keine INI-Datei vorhanden wäre. Diese Zeitunteschiede können schon allein dadurch entstehen, dass beim Schreiben der Schreibkopf der Platte zufällig an eine anderen Position war. Betrachtet man die jeweils ersten Messungen Deiner Messreihen, so beträgt die Zeitdifferenz immerhin immense 2 tausendstel Sekunden. Ausserdem kann man an den Werten der Messreihen erkennen, dass nach der ersten Messung der Cache sehr stark "zuschlägt". Wann die Daten letztlich physikalisch auf der Platte landen, läßt sich durch Deine Messungen nicht feststellen. Die Zeitunterschiede, die Du gemessen hast, sind bereits durch die möglichen Differenzen, die sich aus der durchschnittlichen Zugriffszeit der Festplatte ergeben können, vollständig erklärbar. Schreibst Du da eigentlich ein Programm, das eine Echtzeitverarbeitung durchführt oder eine Anwendung für einen Anwender, der einfach damit arbeiten soll? Beim Ersteren mögen Deine Bedenken eventuell noch nachvollziehbar sein, beim Zweiten halten ich sie für absolut übertrieben. Die Zeitunterschiede sind so kurz, dass Du sie als Anwender nicht bemerken kannst. Das menschliche Wahrnehmungsvermögen ist für so kurze Zeitdifferenzen nicht geeignet. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:44 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz