Technical Article

Optimering af I/O-ydeevne for PDF-behandling i gigabyte-skala

Behandling af standard-PDF'er (1 MB til 10 MB) i Delphi er ligetil ved hjælp af standard stream-klasser som TFileStream eller TMemoryStream. Men når du står over for opgaven med at behandle PDF'er i gigabyte-skala, såsom massive ingeniør-CAD-diagrammer, højopløselige geospatiale kort eller akkumulerede juridiske arkiver, bryder standard hukommelsestildelingsteknikker hurtigt sammen.

Hvis du indlæser en 2 GB PDF i en TMemoryStream i en 32-bit Delphi-applikation, vil du straks ramme en EOutOfMemory-undtagelse. Selv i 64-bit applikationer forårsager dette alvorlige sidefejl (page faulting) og får serveren til at gå i stå. I denne artikel vil vi undersøge, hvordan man optimerer I/O-ydeevne for massive filer ved hjælp af hukommelseskortlagte filer (Memory-Mapped Files).

Problemet med standard-streams

Når du bruger TMemoryStream.LoadFromFile, læser operativsystemet filen fra disken, tildeler sekventiel RAM og kopierer dataene dertil. For en 2 GB fil spilder dette 2 GB fysisk RAM og tager betydelig tid bare for disklæsningssløjfen.

Selv brug af TFileStream kan være problematisk, hvis du springer meget rundt i filen (f.eks. parser PDF XRef-tabellen i slutningen af filen og derefter hopper til objekter spredt over hele filen). De kontinuerlige Seek- og Read-kald resulterer i store omkostninger ved kerneovergange.

Løsningen: Hukommelseskortlagte filer

Hukommelseskortlægning (via Windows API-funktionerne CreateFileMapping og MapViewOfFile) beder operativsystemet om at kortlægge filen direkte i applikationens virtuelle adresserum. Du får en peger (pointer) til dataene, og Windows Virtual Memory Manager håndterer at hente dataene ind og ud af fysisk RAM præcist som du får adgang til dem.

Her er hvordan du kan implementere en højtydende hukommelseskortlagt fillæser i Delphi til PDF-parsing:

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;

Hvorfor hukommelseskortlægning dominerer PDF-parsing

PDF er et format til tilfældig adgang. Parseren starter med at læse traileren i slutningen af filen, finder XRef-tabellen og springer derefter tilfældigt til byteforskydninger over hele filen for at indlæse specifikke ordbøger og streams.

Med hukommelseskortlægning:

  1. Zero-Copy: Data kopieres ikke fra kernerum til brugerrum; du læser direkte fra operativsystemets fil-cache.
  2. Øjeblikkelig indlæsning: Åbning af en 2 GB PDF tager millisekunder, da ingen data faktisk læses fra disken, før du derefererer pegeren.
  3. OS-styret sideinddeling: Hvis du kun parser 50 MB data ud af 2 GB-filen, indlæser OS kun de 50 MB i fysisk RAM. Hukommelsesforbruget forbliver lille.

Ved at implementere en brugerdefineret stream-klasse, der understøttes af hukommelseskortlagte filer, kan din Delphi-applikation tygge sig igennem gigabyte-skala PDF'er med lethed, hvilket forbedrer ydeevnen og skalerbarheden dramatisk.

Bemærk: Optimeret I/O-streamhåndtering for massive dokumenter er indbygget direkte i HotPDF VCL Component.