Processar PDFs padrão (1 MB a 10 MB) no Delphi é simples usando classes de fluxo padrão como TFileStream ou TMemoryStream. No entanto, quando você tem a tarefa de processar PDFs em escala de gigabytes, como esquemas CAD de engenharia enormes, mapas geoespaciais de alta resolução ou arquivos legais acumulados, as técnicas padrão de alocação de memória falham rapidamente.
Se você carregar um PDF de 2 GB em um TMemoryStream em um aplicativo Delphi de 32 bits, você atingirá imediatamente uma exceção EOutOfMemory. Mesmo em aplicativos de 64 bits, fazer isso causa falhas de página severas e paralisa o servidor. Neste artigo, exploraremos como otimizar o desempenho de E/S para arquivos massivos usando Arquivos Mapeados em Memória (Memory-Mapped Files).
O Problema com Fluxos Padrão
Quando você usa TMemoryStream.LoadFromFile, o sistema operacional lê o arquivo do disco, aloca RAM sequencial e copia os dados para ela. Para um arquivo de 2 GB, isso desperdiça 2 GB de RAM física e leva um tempo significativo apenas para o loop de leitura do disco.
Mesmo o uso do TFileStream pode ser problemático se você estiver saltando pelo arquivo frequentemente (por exemplo, analisando a tabela XRef do PDF no final do arquivo e, em seguida, saltando para objetos espalhados pelo arquivo). As chamadas contínuas de Seek e Read resultam em alta sobrecarga de transição de kernel.
A Solução: Arquivos Mapeados em Memória
O mapeamento de memória (através das funções da API do Windows CreateFileMapping e MapViewOfFile) solicita ao sistema operacional que mapeie o arquivo diretamente para o espaço de endereço virtual do aplicativo. Você obtém um ponteiro para os dados e o Gerenciador de Memória Virtual do Windows lida com a paginação dos dados dentro e fora da RAM física estritamente conforme você os acessa.
Veja como você pode implementar um leitor de arquivo mapeado em memória de alto desempenho no 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 o Mapeamento de Memória Domina a Análise de PDF
O PDF é um formato de acesso aleatório. O analisador começa lendo o trailer no final do arquivo, encontra a tabela XRef e depois salta aleatoriamente para deslocamentos de bytes em todo o arquivo 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 usuário; você lê diretamente do cache de arquivos do SO.
- Carregamento Instantâneo: Abrir um PDF de 2 GB leva milissegundos, pois nenhum dado é realmente lido do disco até que você desreferencie o ponteiro.
- Paginação Gerenciada pelo SO: Se você analisar apenas 50 MB de dados do arquivo de 2 GB, o sistema operacional carregará apenas esses 50 MB na RAM física. O consumo de memória permanece mínimo.
Ao implementar uma classe de fluxo personalizada apoiada por arquivos mapeados em memória, seu aplicativo Delphi pode processar PDFs em escala de gigabytes com facilidade, melhorando drasticamente o desempenho e a escalabilidade.
Nota: O manuseio de fluxo de E/S otimizado para documentos massivos é integrado diretamente ao HotPDF VCL Component.