Dve minúty na skopírovanie troch stránok z 40-stránkového PDF nie je problém ladenia výkonu. Je to signál, že sa používa nesprávna cesta API. Keď som toto meranie prvykrát videl na ukrážke kopírovania stránok Komponentu HotPDF, moj inštinkt bol pozrieť sa najprv na štruktúru dokumentu, a až potom na kód. Poradie sa ukázalo ako dôležité.
Čo bolo vlastne pomalé
PDF v otazke bol 40-stránkový referenný dokument s neterivillým stromom stránok: viacero medziléhlých uzlov /Pages namiesto jedného plocheho poľa. Pôvodný vzorkový kód volal LoadFromFile, potom budoval nový dokument pomocou BeginDoc, prešiel vybrané čísla stránok, a pri každej iterácii znova načítaval zdrojový dokument z disku, aby vytiahol stránku. To je plná cena parsovania vynásobená počtom stránok, ktoré chcete. 12 MB súbor zasiahol disk šesykrát pre trojstránkovú extrakciu, pretože nikto sa nepozrel, či súbor musí zo staó otvoreny napriec iteráciami.
Druhý prispievateľ bol v kóde neviditelný: LoadFromFile HotPDF rozšíri celú tabuľku križových odkazov a dekomprimuje každý prúd objektov pri nacitani. To je správne správanie pre dokument, ktorý ide zmeniť, ale je to viac práce, než potrebujete, ak chcete iba počet stránok a podmnožinu stránok. Pre pristený prístup k štruktúre DAOpenFileReadOnly sa vyhne deserializácii plného objektového stromu, čo zaleží na komprimovaných súboroch s veľkými obrázkovými zdrojmi.
Ani jedno z týchto nie je chyba knižnice. Obe sú prípady, kde volaníci vyberia API navrhnuté pre jednu prácu a použijú ho pre inú.
Použitie InsertPagesFromDocument na extrakciu stránok
Správna cesta pre kopírovanie rozsahu stránok z jedného dokumentu HotPDF do druhého je InsertPagesFromDocument, volané po LoadFromFile na zdrojovom. Načítate zdroj raz, načítate alebo vytvoríte cieľ raz, presuniete stránky a uložíte. Zdroj zostane v pamäti napriec všetkými vkladaními stránok:
procedure ExtractPages(const SourceFile, DestFile: string;
const PageRange: string);
var
Source, Dest: THotPDF;
begin
Source := THotPDF.Create(nil);
Dest := THotPDF.Create(nil);
try
// Load source once: full parse happens here and only here
Source.LoadFromFile(SourceFile);
// Build a minimal destination document
Dest.FileName := DestFile;
Dest.BeginDoc;
// Copy the requested range; '1-3' inserts pages 1 through 3
// starting at position 1 in the destination
Dest.InsertPagesFromDocument(Source, PageRange, 1);
Dest.EndDoc;
finally
Source.Free;
Dest.Free;
end;
end;
Parameter PageRange pribíja rovnaký formát ako vzorka príkazového riadku: zoznam oddeleny číarkami obsahujúci čísla stránok alebo rozsahy, napr. '1-3' alebo '1,5,7-9'. Stránky majú základ 1. InsertPagesFromDocument kopíruje prúdy obsahu, slovníky zdrojov a geometriu stránky bez dotyku metadát, záložiek alebo vložených príloh, akích nie je odkazované z kopírovaných stránok. Pre trojstránkovú extrakciu zo 40-stránkového dokumentu je to malá pracovná žina.
Časovanie na rovnakom 12 MB súbore, ktorý predtym bežal dve minúty: menej ako 1,5 sekundy s týmto vzorom. Veľká časť tohto času je jedno volánie LoadFromFile. Štruktúra dokumentu je irelevantná, ked ž je tabuľka objektov vyriešená prvýkrat.
Keď je LoadFromFile priliš veľa: Direct File API
Ak potrebujete iba počítať stránky, kontrolovať informácie o dokumente alebo kopírovať súbor bez dotyku jeho obsahu, Direct File API sa úplne vyhne plnému parsovaniu. DAOpenFileReadOnly mapuje tabuľku križových odkazov bez dekomprimovania prúdov objektov, takže počet stránok je O(veľkosť xref) namiesto O(veľkosť súboru):
procedure InspectPDF(const FileName: string);
var
Pdf: THotPDF;
Handle, PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Handle := Pdf.DAOpenFileReadOnly(FileName, '');
if Handle <= 0 then
Exit;
try
PageCount := Pdf.DAGetPageCount(Handle);
Writeln('Pages: ', PageCount);
// DACopyFile is a byte-preserving copy, no re-serialization
Pdf.DACopyFile(FileName, 'archive-copy.pdf');
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
Upozornenie: DAOpenFileReadOnly akceptuje parameter hesla, ale pre šifrované vstupy sa vráti k plnému parsovaniu, pretože dešifrovanie vyžaduje objektový strom na vyriešenie slovníka šifrovania. Ak sú vaše zdrojové súbory zaifrované, dechífrujte ich najprv pomocou DecryptFile a získte dešifrovanú kópiu, potom ju otvorte Direct File API. Funkcia na úrovni súboru DecryptFile pre štandardné šifrovanie berie priamu cestu prepisovania AES-256 a je rýchlejšia ako LoadFromFile následované SaveLoadedDocument pre veľké súbory, pretože nevytvára plný model objektov v pamäti.
Pamäť pri spracovaní veľkých dávok
Dávkové úlohy, ktoré spracovávajú desiačky súborov v slučke, majú vzor, ktorý vyzerá správne, ale akumuluje pamäť: vytvorenie THotPDF vo vnútri slučky, volánie LoadFromFile, vykonanie práce, volánie Free. To je štruktúrne v poriadku. Problém nastáva, keď vnútorná práca alokuje pomocné objekty, chytá výnimky a necháva tieto pomocné objekty žiš na chybových cestách. Správca pamäti Delphi nekom paktuje, takže sto chybových únikov cez dávkové spustenie môže tlaknúť pamäť dostatoane vysoko, aby spomalila alokáciu pre všetko ostatné.
Oprava nie je exotická. Každý THotPDF a každý medzilaný TStream alebo TBitmap, ktorý sa zúčastňuje PDF práce, patrí do bloku try/finally, kde Free je posledný výrok. Nastavte lokálne ukazovatele na nil pred try, aby vetva finally mohla bezpečne používať if Assigned(x) then x.Free, keď inicializácia zlyhá v strede. Je to štandardná disciplína vlastníctva Delphi a je to celý príbeh pre túto triedu problémov.
Ešte jedna vec na kontrolu v dávkkových kontextoch: AddImage registruje obrázky vo vnútorném zozname, ktorý pretrváva po dobu života inštancie THotPDF. Ak opakovane používate jednu inštanciu cez veľa dokumentov voláním LoadFromFile, registrácie obrázkov z predškádzúcich dokumentov zostanú v zozname. Buď vytvorte novú inštanciu na dokument, alebo zavolajte cestu čistenia zoznamu obrázkov medzi dokumentmi.
Meranie pred akoukoľvek zmenou
Pred použitím niektorého z týchto vzorov zmerajte. TStopwatch Delphi z System.Diagnostics obali QueryPerformanceCounter a je dostatoane presný pre profilovanie wall-clock času I/O súborov. Zabaľte samotné LoadFromFile a pozrite, koľko trvá. Ak tvo rí 90% celkového času, opravou je Direct File API alebo znleníe počtu parsovaní rovnakého súboru. Ak je menej ako 20%, úzkym hrdlom je niekde inde a následujete nesprávnu vec.
Dvojminútová extrakcia, ktorá začala tento príspevok, sa ukázala byť celá vzor opakovaného načítania. Štruktúra dokumentu neprispiela ničím; plosí strom stránok by bežal rovnako. Prepnutie na jedno LoadFromFile následované jedným voláním InsertPagesFromDocument ho priviedlo na 1,3 sekundy na rovnakom hardvéri bez dotyku niečoho iného.
API manipulácie stránok ukázané tu je súčasťou Komponentu HotPDF pre Delphi a C++Builder.