Technical Article

I/O-suorituskyvyn optimointi gigatavuluokan PDF-käsittelyssä

Tavallisten PDF-tiedostojen (1 Mt - 10 Mt) käsittely Delphissä on suoraviivaista käyttämällä vakiomuotoisia virtaluokkia, kuten TFileStream tai TMemoryStream. Kun tehtävänäsi on kuitenkin käsitellä gigatavuluokan PDF-tiedostoja, kuten massiivisia teknisiä CAD-kaavioita, korkearesoluutioisia paikkatietokarttoja tai kertyneitä oikeudellisia arkistoja, vakiomuotoiset muistinvaraustekniikat hajoavat nopeasti.

Jos lataat 2 Gt:n PDF-tiedoston TMemoryStream-virtaan 32-bittisessä Delphi-sovelluksessa, törmäät välittömästi EOutOfMemory-poikkeukseen. Jopa 64-bittisissä sovelluksissa tämä aiheuttaa vakavia sivuvirheitä ja pysäyttää palvelimen kokonaan. Tässä artikkelissa tutkimme, kuinka massiivisten tiedostojen I/O-suorituskykyä voidaan optimoida käyttämällä muistikartoitettuja tiedostoja (Memory-Mapped Files).

Vakiovirtojen ongelma

Kun käytät TMemoryStream.LoadFromFile-metodia, käyttöjärjestelmä lukee tiedoston levyltä, varaa peräkkäistä RAM-muistia ja kopioi datan sinne. 2 Gt:n tiedoston kohdalla tämä tuhlaa 2 Gt fyysistä RAM-muistia ja vie huomattavasti aikaa pelkkään levynlukusilmukkaan.

Jopa TFileStream-luokan käyttö voi olla ongelmallista, jos hyppäät tiedostossa usein paikasta toiseen (esim. PDF:n XRef-taulukon jäsentäminen tiedoston lopussa ja sitten hyppääminen ympäri tiedostoa siroteltuihin objekteihin). Jatkuvat Seek- ja Read-kutsut johtavat suuriin ytimen siirtymien yleiskustannuksiin.

Ratkaisu: Muistikartoitetut tiedostot

Muistikartoitus (Windows-sovellusliittymän funktioiden CreateFileMapping ja MapViewOfFile kautta) pyytää käyttöjärjestelmää kartoittamaan tiedoston suoraan sovelluksen virtuaaliseen osoiteavaruuteen. Saat osoittimen dataan, ja Windowsin virtuaalimuistinhallinta huolehtii datan sivuttamisesta fyysiseen RAM-muistiin ja sieltä pois tiukasti sitä mukaa, kun käytät sitä.

Näin voit toteuttaa suuren suorituskyvyn muistikartoitetun tiedostonlukijan Delphissä PDF-jäsennystä varten:

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;

Miksi muistikartoitus on ylivoimainen PDF-jäsennyksessä

PDF on satunnaissaantimuoto. Jäsennin aloittaa lukemalla trailerin tiedoston lopussa, löytää XRef-taulukon ja hyppää sitten satunnaisesti tavusiirtymiin eri puolilla tiedostoa ladatakseen tiettyjä sanakirjoja ja virtoja.

Muistikartoituksen avulla:

  1. Nollakopiointi: Dataa ei kopioida ydintilasta käyttäjätilaan; luet suoraan käyttöjärjestelmän tiedostovälimuistista.
  2. Välitön lataus: 2 Gt:n PDF-tiedoston avaaminen kestää millisekunteja, koska levyltä ei tosiasiassa lueta dataa ennen kuin purat osoittimen viittauksen.
  3. Käyttöjärjestelmän hallitsema sivutus: Jos jäsentät 2 Gt:n tiedostosta vain 50 Mt dataa, käyttöjärjestelmä lataa fyysiseen RAM-muistiin vain kyseiset 50 Mt. Muistin kulutus pysyy pienenä.

Toteuttamalla mukautetun virtaluokan, joka perustuu muistikartoitettuihin tiedostoihin, Delphi-sovelluksesi pureskelee gigatavuluokan PDF-tiedostoja helposti, mikä parantaa dramaattisesti suorituskykyä ja skaalautuvuutta.

Huomautus: Optimoitu I/O-virtojen käsittely massiivisille asiakirjoille on sisäänrakennettu suoraan HotPDF VCL -komponenttiin.