El procesamiento de archivos PDF estándar (de 1 MB a 10 MB) en Delphi es sencillo si se utilizan 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 estándar de asignación de memoria se colapsan rápidamente.
Si carga un PDF de 2 GB en un TMemoryStream en una aplicación Delphi de 32 bits, inmediatamente encontrará 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 mapeados 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 requiere un tiempo significativo solo para el ciclo de lectura del disco.
Incluso el uso de TFileStream puede ser problemático si salta con frecuencia por el archivo (por ejemplo, analizar la tabla XRef del PDF al final del archivo, y luego saltar a objetos dispersos a lo largo del archivo). Las llamadas continuas a Seek y Read dan como resultado una alta sobrecarga de transición del núcleo.
La solución: Archivos mapeados en memoria
El mapeo de memoria (a través de las funciones de la API de Windows CreateFileMapping y MapViewOfFile) solicita al sistema operativo que asigne el archivo directamente al espacio de direcciones virtuales de la aplicación. Usted 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 los accede.
Así es como puede implementar un lector de archivos mapeado en memoria de alto rendimiento en Delphi para analizar 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é el mapeo de memoria domina el análisis de PDF
PDF es un formato de acceso aleatorio. El analizador comienza leyendo el final del archivo (trailer), encuentra la tabla XRef y luego salta aleatoriamente a los desplazamientos de bytes en todo el archivo para cargar diccionarios y flujos específicos.
Con el mapeo de memoria:
- Zero-Copy (Cero copias): Los datos no se copian del espacio del núcleo al espacio del usuario; usted lee directamente de la caché de archivos del sistema operativo.
- Carga instantánea: Abrir un PDF de 2 GB toma milisegundos, ya que en realidad no se lee ningún dato del disco hasta que se anula la referencia del puntero.
- Paginación administrada por el sistema operativo: Si solo analiza 50 MB de datos del 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 mapeados 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 del flujo de E/S para documentos masivos está integrado directamente en el HotPDF VCL Component.