![]() |
Effizientes Einlesen und Verarbeiten von Textdatei
Hallo *.*.
Gegeben ist eine Textdatei, konkret im PEM-Format (BASE64 codiert). Ziel ist es, alle Blöcke dieser Datei einzulesen in ein Byte-Array - letztlich muss jeder Block in eine ![]()
Delphi-Quellcode:
Die Methoden StringsBetween() und DeleteMultiple() hab ich ergänzt, und die Namen sollten selbsterklärend sein, aber sicherheitshalber hier noch deren Deklaration und Inhalt:
procedure NameNotRelevant;
var Lsl: TMyStringList; LidxH, LidxF: integer; Lstr: string; begin Lsl:= TMyStringList.Create; try Lsl.LoadFromFile(FFileName); repeat LidxH:= Lsl.IndexOf(PEM_HEADER); LidxF:= Lsl.IndexOf(PEM_FOOTER); if LidxH < 0 then NameNotRelevant2(Lstr) else begin Lstr:= Trim(Lsl.StringsBetween(LidxH+1, LidxF-1)); NameNotRelevant2(Lstr); Lsl.DeleteMultiple(LidxF-LidxH+1, LidxH); end; until LidxH < 0; finally Lsl.Free; end; end; procedure NameNotRelevant2(const ACertStr: string); var Lbin: TBytes; Lblob: CERT_BLOB; begin if Length(ACertStr) = 0 then Exit; Lblob.cbData:= (Length(ACertStr)) * SizeOf(Char); SetLength(Lbin, Lblob.cbData); Move(ACertStr[1], Lbin[0], Lblob.cbData); Lblob.pbData:= LPByte(Lbin); // Do something with Lblob (* GetMem(Lblob.pbData, Lblob.cbData+1); try Move(ACertStr[1], Lblob.pbData, Lblob.cbData); finally FreeMem(Lblob.pbData); end;*) end;
Delphi-Quellcode:
Irgendwie finde ich die Sache ineffizent. Daher folgende Fragen:
function TMyStringList.StringsBetween(AIndexFrom, AIndexTo: integer): string;
var i: integer; begin Result:= ''; if (AIndexFrom < 0) OR (AIndexTo >= Self.Count) then Exit; for i:= AIndexFrom to AIndexTo do Result:= Result + Self.Strings[i]; end; function TMyStringList.DeleteMultiple(ACount: integer; AIndex: integer = 0): integer; var i: integer; begin Result:= -1; if (AIndex < 0) OR (AIndex >= Self.Count) then Exit; if (ACount >= Self.Count) then begin Result:= Self.Count; Clear; end else begin for i:= 1 to ACount do begin Self.Delete(AIndex); Inc(Result); end; end; end;
Grüße Dalai |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Hallo,
inwiefern ineffizient? Vermutest Du oder hast Du gemessen? Was die Laufzeit betrifft denke ich, dass LoadFromFile die allermeiste Zeit benötigt. Deinen Code verstehe ich nicht, aber das "until Lidx < 0;" kommt mir komisch vor. Lidx kommt sonst nicht vor. |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
Ich denke dann gibst Du bei Move einen Parameter falsch an. |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Delphi-Quellcode:
Man sieht dass du den Code etwas gekürzt/vereinfacht hast um ihn hier zu posten von daher trifft meine Kritik unter Umständen nicht zu, aber
procedure NameNotRelevant2(const ACertStr: string);
var Lbin: TBytes; Lblob: CERT_BLOB; begin if Length(ACertStr) = 0 then Exit; Lblob.cbData:= (Length(ACertStr)) * SizeOf(Char); SetLength(Lbin, Lblob.cbData); Move(ACertStr[1], Lbin[0], Lblob.cbData); Lblob.pbData:= LPByte(Lbin);
Delphi-Quellcode:
Wird nur innerhalb dieser Methode funktionieren. Wird auf den CERT_BLOB außerhalb dieser Methode zugegriffen ist das TBytes Array nicht mehr gültig und lBlob.pbData zeigt auf ungültige Daten.
.
Lblob.pbData:= LPByte(Lbin); So geht der direkte Weg ohne TBytes:
Delphi-Quellcode:
Du musst dann aber dran denken lBlob.pbData mit FreeMem freizugeben, wenn der BLOB nicht mehr gebraucht wird.
procedure NameNotRelevant2(const ACertStr: string);
var Lblob: CERT_BLOB; begin if Length(ACertStr) = 0 then Exit; Lblob.cbData:= (Length(ACertStr)) * SizeOf(Char); GetMem(Lblob.pbData, Lblob.cbData); Move(ACertStr[1], Lblob.pbData^, Lblob.cbData); EDIT: Ich weiß zwar nicht was du da machst, aber solltest du den Base64 String nicht vorher decoden und dann erst dem Blob zuordnen? |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
Delphi-Quellcode:
procedure TWinHTTPRequest.SetWinCertificate;
var LStore: HCERTSTORE; LCertContext: PCCERT_CONTEXT; LBlob: CRYPT_DATA_BLOB; LBytes: TBytes; begin if (FClientCertPath = '') and (FClientCertificate = nil) then Exit; if FClientCertPath <> '' then LStore := CertOpenStore(PAnsiChar(CERT_STORE_PROV_FILENAME), X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 0, CERT_STORE_OPEN_EXISTING_FLAG or CERT_STORE_READONLY_FLAG, PChar(FClientCertPath)) else begin LBlob.cbData := FClientCertificate.Size; // <--- siehe von hier... SetLength(LBytes, LBlob.cbData); FClientCertificate.Position := 0; FClientCertificate.Read(LBytes, LBlob.cbData); LBlob.pbData := PByte(@LBytes[0]); // <--- ...bis hier! LStore := PFXImportCertStore(@LBlob, PChar(FClientCertPassword), 0); end; if LStore = nil then raise ENetHTTPRequestException.CreateResFmt(@SNetHttpCertFileOpenError, [GetLastError, SysErrorMessage(GetLastError, TWinHttpLib.Handle)]); try LCertContext := CertFindCertificateInStore(LStore, X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, nil, nil); if LCertContext = nil then raise ENetHTTPRequestException.CreateResFmt(@SNetHttpCertNotFoundError, [GetLastError, SysErrorMessage(GetLastError, TWinHttpLib.Handle)]); try WinHttpSetOption(FWRequest, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, LCertContext, SizeOf(CERT_CONTEXT)); finally CertFreeCertificateContext(LCertContext); end; finally CertCloseStore(LStore, 0); end; end;
Delphi-Quellcode:
// mit der Definition aus Winapi.Windows
type _CRYPTOAPI_BLOB = record cbData: DWORD; pbData: PBYTE; end; ... CRYPT_DATA_BLOB = _CRYPTOAPI_BLOB; |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
-Parameter. Daraus schloss ich, dass dessen Adresse verändert werden kann. Was ist denn deiner Meinung nach falsch an dem auskommentierten Move-Aufruf?
var
Grüße Dalai |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
Zitat:
Zitat:
Zitat:
Grüße Dalai |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Effizienter wäre es sicher, wenn das Auswerten der Stringlist ohne IndexOf und Delete gemacht würde. Das sind zusätzliche Speicheroperationen, die nicht nötig wären, wenn man es in etwas so machen würde.
Delphi-Quellcode:
Es geht sicher auch, wie du es machst.
var
Lsl: TStringList; LidxH, LidxF: integer; Lstr, Line: string; I: Integer; begin Lsl:= TStringList.Create; try Lsl.LoadFromFile(FFileName); LidxH := -1; LidxF := -1; Lstr := ''; for I := 0 to lsl.Count -1 do begin Line := lsl[I].Trim; case IndexStr(Line, [PEM_HEADER, PEM_FOOTER]) of 0: LidxH := I; 1: if LidxH>=0 then begin NameNotRelevant2(Lstr); LidxH := -1; LidxF := -1; Lstr := ''; end; else if LidxH >= 0 then Lstr := Lstr + Line; end; end; finally Lsl.Free; end; end; Ob diese Änderung aber schneller ist, kann ich ohne geeignete Daten nicht prüfen. Um die Zeitdauer von bestimmten Operationen zu messen, werwende ich gerne GetTickCount. Das ist zwar nicht genau, aber man bekommt dadurch grob raus, wo es länger dauert. |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
Delphi-Quellcode:
procedure Move(const Source; var Dest; Count: NativeInt);
Wie du siehst sind für Source und Dest keine Datentypen angegeben. Was dort intern passiert ist, dass in der Wahrheit immer ein Pointer dessen übergeben wird was du als Parameter angibst. Im Prinzip wird bei Source und Dest ein unsichtbares "@" vor den übergebenen Wert gesetzt. Das hier:
Delphi-Quellcode:
Move(ACertStr[1], Lblob.pbData^, Lblob.cbData)
ist unter der Haube letztendlich
Delphi-Quellcode:
Move(@ACertStr[1], @(Lblob.pbData^), Lblob.cbData) // Was wirklich übergeben wird
D.h. wenn du bei Lblob.pbData das ^ weglässt schreibt er die Daten nach @Lblob.pbData, was den Inhalt des Pointers selbst überschreibt statt den Speicher auf den der Pointer zeigt. Durch das dereferenzieren bekommst du dann quasi sowas @(Lblob.pbData^) was sich wieder ausgleicht und ausgewertet Lblob.pbData ergibt wodurch dann tatsächlich dahin geschrieben wird, wo der Pointer hinzeigt, statt den Pointer selbst zu überschreiben. Ich hoffe das war halbwegs verständlich (Erklären ist nicht immer so einfach :mrgreen:) |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
Delphi-Quellcode:
var sw := TStopWatch.StartNew;
... Writeln(sw.ElapsedMilliseconds); |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
@Uwe :thumb:
Das hat den Vorteil einer seeeehr genauen Zeitmessung, weil die intern verwendete Funktion
Delphi-Quellcode:
einen "High Resolution Counter" mit einer Genauigkeit von 0.00083 ms (!) abfragt.
QueryPerformanceCounter
Gruß, Andreas |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
----- Zwischenzeitlich hatte ich auch einen anderen Weg getestet, bei dem keine Strings aus der Liste gelöscht werden und stattdessen eine Variable mit der aktuellen Zeile hochgezählt wird, ab der in der Schleife gesucht wird. Der Unterschied in der Laufzeit war zu vernachlässigen. Mit den ordentlichen Zeitmessungen muss ich mich noch beschäftigen. Grüße Dalai |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Nach ein paar Tests mit TStopwatch und einer absichtlich großen PEM-Datei (etwas über 1 MiB) stelle ich Folgendes fest:
Das Verarbeiten der Textdatei (nach LoadFromFile!) mittels Löschen von Strings - siehe OP - dauert um die 200ms, mit dem Hochzählen des Index nur zwischen ca. 20 und 40ms. Ich habe mich nun für das Hochzählen des Index mit folgender Implementation entschieden:
Delphi-Quellcode:
IndexOf hab ich frecherweise aus dem Quellcode von TStrings.IndexOf kopiert und lediglich einen Parameter ergänzt:
procedure NameNotRelevant;
var Lsl: TMyStringList; LidxH, LidxF: integer; Lheaderpresent: Boolean; Lstr: string; begin Lsl:= TMyStringList.Create; try Lsl.LoadFromFile(FFileName); LidxF:= 0; Lheaderpresent:= False; repeat LidxH:= Lsl.IndexOf(PEM_HEADER, LidxF); LidxF:= Lsl.IndexOf(PEM_FOOTER, LidxF+1); if LidxH < 0 then begin if NOT Lheaderpresent then NameNotRelevant2(Lstr); end else begin Lheaderpresent:= True; Lstr:= Trim(Lsl.StringsBetween(LidxH+1, LidxF-1)); NameNotRelevant2(Lstr); Inc(LidxF); end; until LidxH < 0; finally Lsl.Free; end; end; procedure NameNotRelevant2(const ACertStr: string); var Lblob: CERT_BLOB; begin if Length(ACertStr) = 0 then Exit; Lblob.cbData:= Length(ACertStr) * SizeOf(Char); GetMem(Lblob.pbData); try Move(ACertStr[1], Lblob.pbData^, Lblob.cbData); // Do something with Lblob finally FreeMem(Lblob.pbData); end; end;
Delphi-Quellcode:
Wieso gibt's davon eigentlich standardmäßig keine solche Überladung?
function TMyStringList.IndexOf(const S: string; StartIndex: integer): integer;
begin for Result := StartIndex to GetCount - 1 do if AnsiCompareText(Get(Result), S) = 0 then Exit; Result := -1; end; ----- Wenn jemand eine schnellere Variante hat, kann die gern gepostet werden. Auf alle Fälle danke ich allen Postern für die rege Beteiligung! :dp: Grüße Dalai |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Musst Du auf Groß-/Kleinschreibung achten bzw. nicht beachten?
Sind die zu suchenden Zeichenfolgen (PEM_HEADER bzw. PEM_FOOTER immer gleich geschrieben? Im zweiten Fall könntest Du das AnsiCompareText weglassen. Das Dauer durchaus auch seine Zeit. |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Ich würde einen Ansatz per TStreamReader vorschlagen. Dabei wird nicht erst die ganze Datei in den Speicher geladen und es sind weder IndexOf noch StringsBetween nötig:
Delphi-Quellcode:
uses
System.SysUtils, System.Classes; ... procedure NameNotRelevant; var reader: TStreamReader; line: string; data: string; begin reader := TStreamReader.Create(FFileName); try data := ''; while not reader.EndOfStream do begin line := reader.ReadLine; if line = PEM_HEADER then begin data := ''; end else if line = PEM_FOOTER then begin NameNotRelevant2(data); data := ''; end else begin data := data + TrimRight(line); end; end; finally reader.Free; end; end; |
AW: Effizientes Einlesen und Verarbeiten von Textdatei
Zitat:
Die Variante hat noch einen weiteren Nachteil: Dateien ohne Header werden nicht berücksichtigt. Ja, das hatte ich nicht erwähnt, und deshalb konnte das keiner sonst wissen. Zitat:
Zitat:
Grüße Dalai |
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:13 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