Obdelava standardnih PDF dokumentov (od 1 MB do 10 MB) v Delphiju je preprosta z uporabo standardnih razredov tokov, kot sta TFileStream ali TMemoryStream. Vendar pa, ko imate nalogo obdelati gigabajtne PDF datoteke, kot so masivne inženirske sheme CAD, visokoločljivostne geoprostorske karte ali zbrani pravni arhivi, standardne tehnike dodeljevanja pomnilnika hitro odpovejo.
Če naložite 2 GB veliko PDF datoteko v TMemoryStream v 32-bitni Delphi aplikaciji, boste takoj naleteli na izjemo EOutOfMemory. Tudi v 64-bitnih aplikacijah to povzroči hudo napako strani (page faulting) in zaustavi strežnik. V tem članku bomo raziskali, kako optimizirati V/I zmogljivost za masivne datoteke z uporabo v pomnilnik preslikanih datotek (Memory-Mapped Files).
Težava s standardnimi tokovi
Ko uporabite TMemoryStream.LoadFromFile, OS prebere datoteko z diska, dodeli zaporedni pomnilnik RAM in vanj kopira podatke. Za datoteko velikosti 2 GB to zapravi 2 GB fizičnega RAM-a in vzame precej časa samo za zanko branja diska.
Tudi uporaba TFileStream je lahko problematična, če pogosto preskakujete po datoteki (npr. razčlenjevanje tabele XRef PDF dokumenta na koncu datoteke in nato skok na objekte, raztresene po celotni datoteki). Nenehni klici Seek in Read povzročijo visoko režijo prehodov jedra (kernel transition overhead).
Rešitev: V pomnilnik preslikane datoteke
Preslikava pomnilnika (prek funkcij Windows API CreateFileMapping in MapViewOfFile) prosi operacijski sistem, da datoteko preslika neposredno v virtualni naslovni prostor aplikacije. Dobite kazalec na podatke, upravitelj virtualnega pomnilnika Windows (Virtual Memory Manager) pa poskrbi za prenos podatkov v in iz fizičnega RAM-a izključno takrat, ko do njih dostopate.
Tukaj je prikazano, kako lahko v Delphiju implementirate visoko zmogljiv bralnik v pomnilnik preslikanih datotek za razčlenjevanje PDF datotek:
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;
Zakaj preslikava pomnilnika dominira pri razčlenjevanju PDF
PDF je format z naključnim dostopom. Razčlenjevalnik začne z branjem trailerja na koncu datoteke, najde tabelo XRef in nato naključno skače na bajtne odmike po celotni datoteki, da naloži specifične slovarje in tokove.
S preslikavo pomnilnika:
- Brez kopiranja (Zero-Copy): Podatki se ne kopirajo iz prostora jedra v uporabniški prostor; berete neposredno iz datotečnega predpomnilnika operacijskega sistema.
- Takojšnje nalaganje: Odpiranje 2 GB velike PDF datoteke traja le nekaj milisekund, saj se dejansko nobeni podatki ne preberejo z diska, dokler ne dereferencirate kazalca.
- OS upravlja ostričenje (OS Managed Paging): Če iz 2 GB velike datoteke razčlenite le 50 MB podatkov, bo operacijski sistem naložil samo teh 50 MB v fizični RAM. Poraba pomnilnika ostane minimalna.
Z implementacijo po meri prilagojenega razreda tokov, ki ga podpirajo v pomnilnik preslikane datoteke, lahko vaša Delphi aplikacija z lahkoto prebavi gigabajtne PDF datoteke, kar dramatično izboljša zmogljivost in razširljivost.
Opomba: Optimizirano obravnavanje V/I tokov za masivne dokumente je vgrajeno neposredno v komponento HotPDF VCL Component.