Zpracování standardních PDF souborů (1 MB až 10 MB) v Delphi je přímočaré pomocí standardních tříd streamů, jako jsou TFileStream nebo TMemoryStream. Když však dostanete za úkol zpracovat PDF v řádu gigabajtů, jako jsou masivní inženýrská CAD schémata, geoprostorové mapy s vysokým rozlišením nebo nahromaděné právní archivy, standardní techniky alokace paměti rychle selhávají.
Pokud načtete 2GB PDF do TMemoryStream ve 32bitové aplikaci v Delphi, okamžitě narazíte na výjimku EOutOfMemory. I v 64bitových aplikacích způsobí takový krok vážné výpadky stránek (page faulting) a může zcela zastavit server. V tomto článku prozkoumáme, jak optimalizovat výkon I/O pro masivní soubory pomocí paměťově mapovaných souborů.
Problém se standardními streamy
Při použití metody TMemoryStream.LoadFromFile operační systém načte soubor z disku, alokuje sekvenční RAM a zkopíruje do ní data. U 2GB souboru to zbytečně plýtvá 2 GB fyzické paměti RAM a zabírá značný čas už jen pro samotný cyklus čtení z disku.
Dokonce i použití TFileStream může být problematické, pokud v souboru často přeskakujete (např. analyzujete tabulku PDF XRef na konci souboru a poté přeskakujete na objekty rozeseté po celém souboru). Neustálá volání Seek a Read vedou k vysoké režii při přechodech jádra.
Řešení: Paměťově mapované soubory
Mapování paměti (prostřednictvím funkcí Windows API CreateFileMapping a MapViewOfFile) žádá operační systém o mapování souboru přímo do virtuálního adresního prostoru aplikace. Získáte ukazatel na data a Správce virtuální paměti Windows se postará o stránkování dat do fyzické paměti RAM a zpět striktně podle toho, jak k nim přistupujete.
Zde je ukázka, jak můžete v Delphi implementovat vysoce výkonnou čtečku paměťově mapovaných souborů pro analýzu 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;
Proč mapování paměti dominuje při zpracování PDF
PDF je formát s náhodným přístupem. Analyzátor začne načtením traileru na konci souboru, najde tabulku XRef a poté náhodně přeskakuje na bajtové posuny (offsets) v celém souboru, aby načetl specifické slovníky a streamy.
Díky mapování paměti:
- Nulové kopírování: Data se nekopírují z prostoru jádra do uživatelského prostoru, čtete je přímo z mezipaměti souborů operačního systému.
- Okamžité načítání: Otevření 2GB PDF trvá milisekundy, protože ze samotného disku se ve skutečnosti žádná data nečtou, dokud neodkazujete na daný ukazatel.
- Stránkování řízené OS: Pokud z 2GB souboru analyzujete pouze 50 MB dat, operační systém načte do fyzické paměti RAM pouze těchto 50 MB. Spotřeba paměti zůstává nepatrná.
Implementací vlastní třídy streamu, která je podložena paměťově mapovanými soubory, může vaše aplikace v Delphi snadno zpracovat PDF o velikosti gigabajtů, čímž výrazně zlepšíte výkon i škálovatelnost.
Poznámka: Optimalizované zpracování I/O streamů pro masivní dokumenty je zabudováno přímo v komponentě HotPDF VCL Component.