O processamento de PDFs normais (1MB a 10MB) em Delphi é simples utilizando classes de fluxo (stream) padrão como TFileStream ou TMemoryStream. No entanto, quando tem a tarefa de processar PDFs à escala de gigabytes, tais como esquemas CAD de engenharia massivos, mapas geoespaciais de alta resolução ou arquivos legais acumulados, as técnicas padrão de alocação de memória falham rapidamente.
Se carregar um PDF de 2GB para um TMemoryStream numa aplicação Delphi de 32 bits, irá imediatamente deparar-se com uma exceção EOutOfMemory. Mesmo em aplicações de 64 bits, fazê-lo causa falhas de página severas (page faulting) e paralisa o servidor. Neste artigo, exploraremos como otimizar o desempenho de I/O para ficheiros massivos utilizando Ficheiros Mapeados em Memória (Memory-Mapped Files).
O Problema com os Fluxos Padrão
Quando utiliza TMemoryStream.LoadFromFile, o sistema operativo lê o ficheiro do disco, aloca RAM sequencial e copia os dados para ela. Para um ficheiro de 2GB, isto desperdiça 2GB de RAM física e demora um tempo significativo apenas para o ciclo de leitura do disco.
Mesmo utilizar TFileStream pode ser problemático se estiver frequentemente a saltar pelo ficheiro (por exemplo, analisar a tabela XRef do PDF no final do ficheiro e depois saltar para objetos espalhados por todo o ficheiro). As chamadas contínuas de Seek e Read resultam numa elevada sobrecarga de transição do kernel.
A Solução: Ficheiros Mapeados em Memória
O mapeamento de memória (através das funções da API do Windows CreateFileMapping e MapViewOfFile) pede ao sistema operativo para mapear o ficheiro diretamente no espaço de endereçamento virtual da aplicação. Obtém um ponteiro para os dados, e o Gestor de Memória Virtual do Windows trata de colocar e retirar os dados da RAM física estritamente à medida que acede a eles.
Eis como pode implementar um leitor de ficheiros mapeados em memória de alto desempenho em Delphi para análise 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 Que Razão o Mapeamento de Memória Domina a Análise de PDF
O PDF é um formato de acesso aleatório. O analisador começa por ler o trailer no final do ficheiro, encontra a tabela XRef e depois salta aleatoriamente para deslocamentos de bytes (byte offsets) em todo o ficheiro para carregar dicionários e fluxos específicos.
Com o mapeamento de memória:
- Cópia Zero (Zero-Copy): Os dados não são copiados do espaço do kernel para o espaço do utilizador; lê diretamente da cache de ficheiros do sistema operativo.
- Carregamento Instantâneo: Abrir um PDF de 2GB demora milissegundos, uma vez que nenhuns dados são realmente lidos do disco até que desreferencie o ponteiro.
- Paginação Gerida pelo SO: Se analisar apenas 50MB de dados de um ficheiro de 2GB, o sistema operativo apenas carrega esses 50MB para a RAM física. O consumo de memória permanece mínimo.
Ao implementar uma classe de fluxo personalizada apoiada por ficheiros mapeados em memória, a sua aplicação Delphi pode processar PDFs à escala de gigabytes com facilidade, melhorando drasticamente o desempenho e a escalabilidade.
Nota: O tratamento otimizado de fluxos de I/O para documentos massivos está integrado diretamente no Componente HotPDF VCL.