Technical Article

Optimizacija IO performansi za obradu PDF datoteka u gigabajtnim razmerama

Obrada standardnih PDF-ova (od 1MB do 10MB) u Delphiju je jednostavna pomoću standardnih klasa tokova kao što su TFileStream ili TMemoryStream. Međutim, kada dobijete zadatak da obradite PDF-ove gigabajtnih razmera, kao što su masivne inženjerske CAD šeme, geoprostorne mape visoke rezolucije ili akumulirane pravne arhive, standardne tehnike alokacije memorije brzo otkazuju.

Ako učitate PDF od 2GB u TMemoryStream u 32-bitnoj Delphi aplikaciji, odmah ćete naići na izuzetak EOutOfMemory. Čak i u 64-bitnim aplikacijama, ovo uzrokuje ozbiljne greške stranica i zaustavlja server. U ovom članku ćemo istražiti kako optimizovati I/O performanse za masivne datoteke pomoću datoteka mapiranih u memoriji (Memory-Mapped Files).

Problem sa standardnim tokovima

Kada koristite TMemoryStream.LoadFromFile, operativni sistem čita datoteku sa diska, alocira sekvencijalnu RAM memoriju i kopira podatke u nju. Za datoteku od 2GB, ovo troši 2GB fizičke RAM memorije i zahteva značajno vreme samo za petlju čitanja diska.

Čak i korišćenje TFileStream-a može biti problematično ako često skačete po datoteci (npr. parsiranje PDF XRef tabele na kraju datoteke, a zatim skakanje na objekte razbacane po datoteci). Kontinuirani pozivi Seek i Read rezultiraju visokim troškovima tranzicije kernela.

Rešenje: Datoteke mapirane u memoriji

Mapiranje memorije (preko Windows API funkcija CreateFileMapping i MapViewOfFile) traži od operativnog sistema da preslika datoteku direktno u virtuelni adresni prostor aplikacije. Dobijate pokazivač na podatke, a Windows menadžer virtuelne memorije obrađuje stranice podataka u fizičkoj RAM memoriji i iz nje tačno onako kako im pristupate.

Evo kako možete implementirati čitač datoteka mapiranih u memoriji visokih performansi u Delphiju za parsiranje PDF-a:

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;

Zašto mapiranje memorije dominira u parsiranju PDF-a

PDF je format sa nasumičnim pristupom. Parser počinje čitanjem trejlera na kraju datoteke, pronalazi XRef tabelu, a zatim nasumično skače na ofsete bajtova kroz datoteku kako bi učitao specifične rečnike i tokove.

Sa mapiranjem memorije:

  1. Bez kopiranja (Zero-Copy): Podaci se ne kopiraju iz prostora kernela u korisnički prostor; čitate direktno iz keš memorije datoteka operativnog sistema.
  2. Trenutno učitavanje: Otvaranje PDF-a od 2GB traje milisekundi, jer se zapravo nijedan podatak ne čita sa diska dok ne dereferencirate pokazivač.
  3. Strančenje kojim upravlja OS: Ako parsirate samo 50MB podataka iz datoteke od 2GB, OS učitava samo tih 50MB u fizičku RAM memoriju. Potrošnja memorije ostaje minimalna.

Implementacijom prilagođene klase tokova (stream-ova) podržane datotekama mapiranim u memoriji, vaša Delphi aplikacija može s lakoćom da obrađuje PDF-ove gigabajtnih razmera, dramatično poboljšavajući performanse i skalabilnost.

Napomena: Optimizovana obrada I/O tokova za masivne dokumente ugrađena je direktno u HotPDF VCL komponentu.