Die Verarbeitung von Standard-PDFs (1 MB bis 10 MB) in Delphi ist mit Standard-Stream-Klassen wie TFileStream oder TMemoryStream unkompliziert. Wenn Sie jedoch mit der Verarbeitung von PDFs im Gigabyte-Maßstab beauftragt sind – wie massiven CAD-Schaltplänen, hochauflösenden Geodatenkarten oder großen Rechtsarchiven –, stoßen Standard-Speicherzuweisungstechniken schnell an ihre Grenzen.
Wenn Sie ein 2-GB-PDF in einen TMemoryStream in einer 32-Bit-Delphi-Anwendung laden, erhalten Sie sofort eine EOutOfMemory-Ausnahme. Auch in 64-Bit-Anwendungen führt dies zu massiven Seitenfehlern und bringt den Server zum Stillstand. In diesem Artikel untersuchen wir, wie Sie die I/O-Leistung für massive Dateien mithilfe von Memory-Mapped-Dateien optimieren können.
Das Problem mit Standard-Streams
Wenn Sie TMemoryStream.LoadFromFile verwenden, liest das Betriebssystem die Datei von der Festplatte, weist sequenziellen RAM zu und kopiert die Daten dorthin. Bei einer 2-GB-Datei verschwendet dies 2 GB physischen RAM und kostet allein für die Festplatten-Leseschleife erheblich Zeit.
Selbst die Verwendung von TFileStream kann problematisch sein, wenn Sie häufig in der Datei hin- und herspringen (z. B. das Parsen der PDF-XRef-Tabelle am Ende der Datei und dann das Springen zu Objekten, die über die Datei verstreut sind). Die ständigen Seek- und Read-Aufrufe führen zu einem hohen Kernel-Übergangs-Overhead.
Die Lösung: Memory-Mapped-Dateien
Memory Mapping (über die Windows-API-Funktionen CreateFileMapping und MapViewOfFile) fordert das Betriebssystem auf, die Datei direkt in den virtuellen Adressraum der Anwendung abzubilden. Sie erhalten einen Zeiger auf die Daten, und der Windows Virtual Memory Manager kümmert sich um das Ein- und Auslagern der Daten in den physischen RAM genau dann, wenn Sie darauf zugreifen.
Hier sehen Sie, wie Sie einen leistungsstarken Memory-Mapped-Datei-Reader in Delphi für das PDF-Parsing implementieren können:
uses
Winapi.Windows, System.SysUtils, System.Classes;
type
TMemoryMappedFileReader = class
private
FFileHandle: THandle;
FMappingHandle: THandle;
FDataPtr: Pointer;
FFileSize: Int64;
public
constructor Create(const FileName: string);
destructor Destroy; override;
property Data: Pointer read FDataPtr;
property Size: Int64 read FFileSize;
end;
constructor TMemoryMappedFileReader.Create(const FileName: string);
var
HighSize, LowSize: DWORD;
begin
// Open the file with read permissions
FFileHandle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if FFileHandle = INVALID_HANDLE_VALUE then
RaiseLastOSError;
// Get the 64-bit file size
LowSize := GetFileSize(FFileHandle, @HighSize);
FFileSize := (Int64(HighSize) shl 32) or LowSize;
// Create the mapping object
FMappingHandle := CreateFileMapping(FFileHandle, nil, PAGE_READONLY, HighSize, LowSize, nil);
if FMappingHandle = 0 then
RaiseLastOSError;
// Map the file into the virtual address space
FDataPtr := MapViewOfFile(FMappingHandle, FILE_MAP_READ, 0, 0, 0);
if FDataPtr = nil then
RaiseLastOSError;
end;
destructor TMemoryMappedFileReader.Destroy;
begin
if FDataPtr <> nil then UnmapViewOfFile(FDataPtr);
if FMappingHandle <> 0 then CloseHandle(FMappingHandle);
if FFileHandle <> INVALID_HANDLE_VALUE then CloseHandle(FFileHandle);
inherited;
end;
Warum Memory Mapping das PDF-Parsing dominiert
PDF ist ein Random-Access-Format. Der Parser beginnt damit, den Trailer am Ende der Datei zu lesen, findet die XRef-Tabelle und springt dann zufällig zu Byte-Offsets im gesamten Dokument, um bestimmte Wörterbücher und Streams zu laden.
Mit Memory Mapping:
- Zero-Copy: Daten werden nicht vom Kernelbereich in den Benutzerbereich kopiert; Sie lesen direkt aus dem Betriebssystem-Dateicache.
- Sofortiges Laden: Das Öffnen eines 2-GB-PDFs dauert nur Millisekunden, da tatsächlich keine Daten von der Festplatte gelesen werden, bis Sie den Zeiger dereferenzieren.
- Vom Betriebssystem verwaltetes Paging: Wenn Sie nur 50 MB Daten aus der 2-GB-Datei parsen, lädt das Betriebssystem auch nur diese 50 MB in den physischen RAM. Der Speicherverbrauch bleibt winzig.
Indem Sie eine benutzerdefinierte Stream-Klasse implementieren, die auf Memory-Mapped-Dateien basiert, kann Ihre Delphi-Anwendung PDFs im Gigabyte-Maßstab mühelos verarbeiten und so Leistung und Skalierbarkeit drastisch verbessern.
Hinweis: Die optimierte I/O-Stream-Verarbeitung für massive Dokumente ist direkt in die HotPDF VCL-Komponente integriert.