Procesarea PDF-urilor standard (1MB până la 10MB) în Delphi este simplă utilizând clase de flux standard precum TFileStream sau TMemoryStream. Cu toate acestea, atunci când aveți sarcina de a procesa PDF-uri la scară de gigabytes (de exemplu, scheme CAD inginerești masive, hărți geospațiale de înaltă rezoluție sau arhive legale acumulate), tehnicile standard de alocare a memoriei cedează rapid.
Dacă încărcați un PDF de 2GB într-un TMemoryStream într-o aplicație Delphi pe 32 de biți, veți întâmpina imediat o excepție EOutOfMemory. Chiar și în aplicațiile pe 64 de biți, acest lucru cauzează erori severe de paginare (page faulting) și blochează serverul. În acest articol, vom explora cum să optimizăm performanța I/O pentru fișiere masive folosind fișiere mapate în memorie (Memory-Mapped Files).
Problema cu fluxurile standard
Când utilizați TMemoryStream.LoadFromFile, sistemul de operare citește fișierul de pe disc, alocă memorie RAM secvențială și copiază datele în ea. Pentru un fișier de 2GB, acest lucru irosește 2GB de memorie RAM fizică și necesită timp semnificativ doar pentru bucla de citire de pe disc.
Chiar și utilizarea TFileStream poate fi problematică dacă săriți frecvent prin fișier (de exemplu, parsați tabelul XRef PDF la sfârșitul fișierului, apoi săriți la obiecte împrăștiate pe tot parcursul fișierului). Apelurile continue Seek și Read duc la un overhead ridicat de tranziție a nucleului (kernel transition).
Soluția: Fișiere mapate în memorie
Maparea memoriei (prin funcțiile API Windows CreateFileMapping și MapViewOfFile) solicită sistemului de operare să mapeze fișierul direct în spațiul de adrese virtuale al aplicației. Obțineți un pointer către date, iar Windows Virtual Memory Manager se ocupă de paginarea datelor în și din memoria RAM fizică strict pe măsură ce le accesați.
Iată cum puteți implementa în Delphi un cititor de fișiere mapate în memorie de înaltă performanță pentru parsarea PDF-urilor:
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;
De ce maparea memoriei domină parsarea PDF
PDF este un format cu acces aleatoriu. Parserul începe prin citirea trailer-ului la sfârșitul fișierului, găsește tabelul XRef și apoi sare aleatoriu la decalajele de octeți de pe parcursul fișierului pentru a încărca dicționare și fluxuri specifice.
Cu maparea memoriei:
- Copiere zero (Zero-Copy): Datele nu sunt copiate din spațiul kernel în spațiul utilizator; citiți direct din memoria cache de fișiere a sistemului de operare.
- Încărcare instantanee: Deschiderea unui PDF de 2GB durează milisecunde, deoarece nicio dată nu este citită efectiv de pe disc până când nu dereferențiați pointerul.
- Paginare gestionată de SO: Dacă parsați doar 50MB de date dintr-un fișier de 2GB, sistemul de operare încarcă doar acei 50MB în memoria RAM fizică. Consumul de memorie rămâne infim.
Implementând o clasă de flux personalizată susținută de fișiere mapate în memorie, aplicația dumneavoastră Delphi poate procesa cu ușurință PDF-uri la scară de gigabytes, îmbunătățind dramatic performanța și scalabilitatea.
Notă: Gestionarea optimizată a fluxurilor de I/O pentru documente masive este integrată direct în HotPDF VCL Component.