Technical Article

Gigabaitų dydžio PDF failų apdorojimo IO našumo optimizavimas

Standartinių PDF failų (nuo 1 MB iki 10 MB) apdorojimas „Delphi“ aplinkoje yra paprastas naudojant standartines srautų klases, tokias kaip TFileStream ar TMemoryStream. Tačiau, kai tenka apdoroti gigabaitų dydžio PDF failus – pavyzdžiui, didžiules inžinerines CAD schemas, didelės raiškos geoerdvinius žemėlapius ar sukauptus teisinius archyvus – standartiniai atminties paskirstymo metodai greitai nustoja veikti.

Jei 2 GB PDF failą įkelsite į TMemoryStream naudodami 32 bitų „Delphi“ programą, iškart susidursite su EOutOfMemory klaida. Net ir 64 bitų programose tai sukelia didelių puslapių klaidų (angl. page faulting) ir visiškai sustabdo serverio darbą. Šiame straipsnyje panagrinėsime, kaip optimizuoti įvesties / išvesties (I/O) našumą didžiuliams failams naudojant atmintyje atvaizduojamus failus (angl. Memory-Mapped Files).

Standartinių srautų problema

Kai naudojate TMemoryStream.LoadFromFile, operacinė sistema (OS) nuskaito failą iš disko, priskiria nuoseklią RAM atmintį ir nukopijuoja į ją duomenis. 2 GB failui tai išvaisto 2 GB fizinės RAM atminties ir užima daug laiko vien dėl disko skaitymo ciklo.

Netgi TFileStream naudojimas gali sukelti problemų, jei dažnai šokinėjate po failą (pvz., analizuojate PDF „XRef“ lentelę failo pabaigoje, o tada peršokate prie objektų, išsibarsčiusių po visą failą). Nuolatiniai Seek ir Read iškvietimai sukelia dideles branduolio perėjimo (angl. kernel transition) apkrovas.

Sprendimas: atmintyje atvaizduojami failai

Atminties atvaizdavimas (per „Windows API“ funkcijas CreateFileMapping ir MapViewOfFile) prašo operacinės sistemos tiesiogiai atvaizduoti failą programos virtualios atminties erdvėje. Jūs gaunate rodyklę į duomenis, o „Windows“ virtualios atminties tvarkyklė valdo duomenų įkėlimą ir iškėlimą iš fizinės RAM atminties tik tada, kai jūs juos pasiekiate.

Štai kaip galite įdiegti didelio našumo atmintyje atvaizduojamų failų skaitytuvą „Delphi“ aplinkoje PDF failams analizuoti:

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;

Kodėl atminties atvaizdavimas dominuoja PDF analizėje

PDF yra tiesioginės prieigos formatas. Analizatorius pradeda nuo pabaigos (angl. trailer) skaitymo failo gale, randa „XRef“ lentelę ir tada atsitiktinai peršoka prie baitų poslinkių visame faile, kad įkeltų konkrečius žodynus ir srautus.

Naudojant atminties atvaizdavimą:

  1. „Zero-Copy“: Duomenys nėra kopijuojami iš branduolio erdvės į vartotojo erdvę; jūs skaitote tiesiogiai iš OS failų talpyklos.
  2. Momentinis įkėlimas: 2 GB PDF failo atidarymas užtrunka milisekundes, nes jokie duomenys iš tikrųjų nenuskaitomi iš disko, kol nepasinaudojate rodykle.
  3. OS valdomas puslapiavimas: Jei iš 2 GB failo analizuojate tik 50 MB duomenų, OS įkelia tik tuos 50 MB į fizinę RAM atmintį. Atminties sunaudojimas išlieka minimalus.

Įdiegę tinkintą srautų klasę, paremtą atmintyje atvaizduojamais failais, jūsų „Delphi“ programa gali lengvai apdoroti gigabaitų dydžio PDF failus, taip žymiai pagerindama našumą ir mastelio keitimą.

Pastaba: Optimizuotas I/O srautų apdorojimas didžiuliams dokumentams yra tiesiogiai integruotas į „HotPDF VCL“ komponentą.