Technical Article

Optimización del rendimiento de E/S para el procesamiento de PDF a escala de gigabytes

El procesamiento de archivos PDF estándar (de 1 MB a 10 MB) en Delphi es sencillo utilizando clases de flujo estándar como TFileStream o TMemoryStream. Sin embargo, cuando se le asigna la tarea de procesar archivos PDF a escala de gigabytes, como esquemas CAD de ingeniería masivos, mapas geoespaciales de alta resolución o archivos legales acumulados, las técnicas de asignación de memoria estándar se descomponen rápidamente.

Si carga un PDF de 2 GB en un TMemoryStream en una aplicación Delphi de 32 bits, se encontrará inmediatamente con una excepción EOutOfMemory. Incluso en aplicaciones de 64 bits, hacer esto causa fallas de página severas y detiene el servidor. En este artículo, exploraremos cómo optimizar el rendimiento de E/S para archivos masivos utilizando Archivos Asignados en Memoria (Memory-Mapped Files).

El problema con los flujos estándar

Cuando utiliza TMemoryStream.LoadFromFile, el sistema operativo lee el archivo del disco, asigna memoria RAM secuencial y copia los datos en ella. Para un archivo de 2 GB, esto desperdicia 2 GB de RAM física y toma una cantidad de tiempo significativa solo para el bucle de lectura del disco.

Incluso el uso de TFileStream puede ser problemático si salta por el archivo con frecuencia (por ejemplo, analizando la tabla XRef del PDF al final del archivo, luego saltando a objetos dispersos por todo el archivo). Las continuas llamadas a Seek y Read dan como resultado una alta sobrecarga de transición del núcleo.

La solución: archivos asignados en memoria

La asignación en memoria (a través de las funciones API de Windows CreateFileMapping y MapViewOfFile) solicita al sistema operativo que asigne el archivo directamente en el espacio de direcciones virtuales de la aplicación. Obtiene un puntero a los datos y el Administrador de Memoria Virtual de Windows maneja la paginación de los datos dentro y fuera de la RAM física estrictamente a medida que accede a ellos.

A continuación se muestra cómo puede implementar un lector de archivos asignados en memoria de alto rendimiento en Delphi para el análisis de 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;

Por qué la asignación en memoria domina el análisis de PDF

PDF es un formato de acceso aleatorio. El analizador comienza leyendo el tráiler al final del archivo, encuentra la tabla XRef y luego salta aleatoriamente a desplazamientos de bytes en todo el archivo para cargar diccionarios y flujos específicos.

Con la asignación en memoria:

  1. Copia cero: los datos no se copian del espacio del núcleo al espacio del usuario; se leen directamente de la caché de archivos del sistema operativo.
  2. Carga instantánea: abrir un PDF de 2 GB lleva milisegundos, ya que en realidad no se leen datos del disco hasta que se desreferencia el puntero.
  3. Paginación administrada por el sistema operativo: si solo analiza 50 MB de datos de un archivo de 2 GB, el sistema operativo solo carga esos 50 MB en la RAM física. El consumo de memoria sigue siendo mínimo.

Al implementar una clase de flujo personalizada respaldada por archivos asignados en memoria, su aplicación Delphi puede procesar archivos PDF a escala de gigabytes con facilidad, mejorando drásticamente el rendimiento y la escalabilidad.

Nota: El manejo optimizado de flujos de E/S para documentos masivos está integrado directamente en el componente HotPDF VCL.