Technical Article

Ottimizzare le Prestazioni I/O per l'Elaborazione di PDF su Larga Scala

L'elaborazione di PDF standard (da 1 MB a 10 MB) in Delphi è un'operazione semplice utilizzando le classi stream standard come TFileStream o TMemoryStream. Tuttavia, quando ti viene richiesto di elaborare PDF della dimensione di gigabyte, come enormi schemi CAD ingegneristici, mappe geospaziali ad alta risoluzione o archivi legali accumulati, le tecniche standard di allocazione della memoria falliscono rapidamente.

Se carichi un PDF da 2 GB in un TMemoryStream in un'applicazione Delphi a 32 bit, andrai immediatamente incontro a un'eccezione EOutOfMemory. Anche nelle applicazioni a 64 bit, farlo causa un grave page faulting e blocca il server. In questo articolo, esploreremo come ottimizzare le prestazioni di I/O per file di grandi dimensioni utilizzando i File Mappati in Memoria.

Il Problema con gli Stream Standard

Quando si utilizza TMemoryStream.LoadFromFile, il sistema operativo legge il file dal disco, alloca la RAM sequenziale e vi copia i dati. Per un file da 2 GB, ciò spreca 2 GB di RAM fisica e richiede molto tempo solo per il ciclo di lettura del disco.

Anche l'uso di TFileStream può essere problematico se si salta frequentemente all'interno del file (ad esempio, analizzando la tabella XRef del PDF alla fine del file, per poi saltare agli oggetti sparsi nel file). Le continue chiamate Seek e Read comportano un elevato overhead di transizione del kernel.

La Soluzione: File Mappati in Memoria

La mappatura della memoria (tramite le funzioni API di Windows CreateFileMapping e MapViewOfFile) richiede al sistema operativo di mappare il file direttamente nello spazio di indirizzamento virtuale dell'applicazione. Ottieni un puntatore ai dati e il Gestore della Memoria Virtuale di Windows si occupa del paging dei dati in entrata e in uscita dalla RAM fisica rigorosamente man mano che vi accedi.

Ecco come implementare un lettore di file mappato in memoria ad alte prestazioni in Delphi per l'analisi dei PDF:

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;

Perché la Mappatura della Memoria Domina l'Analisi PDF

Il PDF è un formato ad accesso casuale. Il parser inizia leggendo il trailer alla fine del file, trova la tabella XRef, quindi salta casualmente agli offset in byte in tutto il file per caricare dizionari e flussi specifici.

Con la mappatura in memoria:

  1. Copia Zero: i dati non vengono copiati dallo spazio kernel allo spazio utente; si legge direttamente dalla cache del file del sistema operativo.
  2. Caricamento Istantaneo: l'apertura di un PDF da 2 GB richiede millisecondi, poiché nessun dato viene effettivamente letto dal disco finché non si dereferenzia il puntatore.
  3. Paging Gestito dal Sistema Operativo: se si analizzano solo 50 MB di dati su un file da 2 GB, il sistema operativo carica solo quei 50 MB nella RAM fisica. Il consumo di memoria rimane minimo.

Implementando una classe stream personalizzata supportata da file mappati in memoria, la tua applicazione Delphi può elaborare facilmente PDF da gigabyte, migliorando drasticamente prestazioni e scalabilità.

Nota: la gestione ottimizzata dei flussi di I/O per documenti di grandi dimensioni è integrata direttamente nel Componente VCL HotPDF.