HotPDF RenderCacheFolder mení cache vyrenderovaných strán v pamäti komponentu HotPDF Delphi na perzistentnú diskovú cache strán: vyrenderované strany sa zapisujú ako PNG súbory pod priečinkom, ktorý si vyberiete, a keď sa nabudúce otvorí ten istý PDF zdroj, RenderLoadedPageToBitmapCached si ich prečíta namiesto opätovnej rasterizácie. Poradie hľadania je pamäť, potom disk, potom renderer
Disková vrstva je v API od v2.416.0, ale do v2.770.140 nikdy naozaj nepodala stranu pre bežné volanie LoadFromFile alebo LoadFromStream. Oprava vynútila otázku, ktorú musí zodpovedať každá perzistentná cache: ako viete, že súbor, ktorý dnes otvoríte, je dokument, ktorý ste renderovali včera, a čo sa stane s cachovanými stranami, keď nie? Nižšie sú odpovede, na ktorých sa HotPDF usadil, vrátane miest, kde zámerne odmieta cachovať
Ako funguje disková render cache HotPDF?
Disková render cache HotPDF je druhá vrstva za raster cache v pamäti a účastní sa hry len vtedy, keď je RenderCacheFolder neprázdna cesta. Volanie RenderLoadedPageToBitmapCached(PageIndex, DPI) najprv prescannuje položky v pamäti, kľúčované indexom strany, DPI a variantom render nastavení. Pri minule sa pýta diskovej vrstvy; zásah na disku dekóduje PNG, povýši ho späť do pamäti a vráti kópiu vlastnenú volajúcim. Až keď minú obe vrstvy, ide strana cez interpreter content streamov popísaný v článku o renderovaní načítanej PDF strany do TBitmap a svieža bitmapa sa potom zapisuje aj na disk
Na disku je rozloženie zámerne nudné. Každý dokument dostáva podpriečinok pomenovaný z 16-hex-znakového dokumentového kľúča plus 16-hex-znakového render variantu, každá strana sa ukladá ako <page>@<dpi>.png a index.txt v koreni drží dokumenty v poradí naposledy použitých za schémovým tagom. Nesúhlas schémy pri prvom použití priečinok vyčistí. Zápisy idú najprv do dočasného súboru a na miesto sa vymenia atomickou náhradou, takže pád uprostred zápisu zanechá buď starú stranu, alebo nič, nikdy polovicu PNG. PNG, ktoré sa nedá dekódovať, sa zmaže a počíta sa ako minule
Priečinok ohraničujú tri limity:
RenderCacheMaxDocuments(predvolené 20) kapuje počet podpriečinkov dokumentov; najdlhšie nepoužitý priečinok sa vyhadzuje ako prvýRenderCacheMaxBytes(predvolené 524288000, čo je 500 MB) kapuje celkovú veľkosť všetkých PNG súborov pod koreňom- Každý priečinok dokumentu drží najviac 200 obrazov strán; ten limit na dokument je fixovaný v THotPDF a nie je publikovanou property
RenderCacheCapacity (predvolené 8) je samostatný gombík: nastavuje, koľko vyrenderovaných strán drží vrstva v pamäti a s diskovou stopou nesúvisí nič
uses
SysUtils, Graphics, HPDFDoc;
procedure WarmThumbnails(const FileName: string);
var
Pdf: THotPDF;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
// Nastavte diskovú vrstvu pred prvým cachovaným renderom:
// priečinok aj oba limity sa čítajú pri prvom použití vrstvy
Pdf.RenderCacheFolder := IncludeTrailingPathDelimiter(
GetEnvironmentVariable('LOCALAPPDATA')) + 'MyViewer\PageCache';
Pdf.RenderCacheMaxDocuments := 50;
Pdf.RenderCacheMaxBytes := Int64(1024) * 1024 * 1024; // 1 GiB
Pdf.RenderCacheCapacity := 16; // strany v pamäti
if Pdf.LoadFromFile(FileName) > 0 then
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 96);
if Bmp <> nil then
try
// Kópiu podajte tu pásu miniatúr
finally
Bmp.Free; // cachované volanie vždy vracia kópiu vlastnenú volajúcim
end;
end;
finally
Pdf.Free; // od v2.770.140 toto už nemazá diskové položky
end;
end;
Pustite tú istú procedúru dvakrát a druhý beh nerasterizuje žiadnu stranu, ktorá sa zmestila do cache. Objekt diskovej cache sa vytvára lazily pri prvom cachovanom rendere a žije, kým sa neuvolní inštancia THotPDF, takže zmena RenderCacheFolder, RenderCacheMaxDocuments alebo RenderCacheMaxBytes po tomto momente nepresunie ani nezmení už otvorenú cache. Strany príliš veľké pre politiku príjmu do pamäti (predvolene jedna položka nesmie presiahnuť 64 MiB 32-bitových pixelov) sa tiež nepersistujú a disková vrstva sa konzultuje len kým RenderFallbackPolicy drží svoje predvolené rfpIgnore, lebo fallback diagnostika sa neukladá popri PNG
Prečo RenderCacheFolder nefungoval nikdy pred v2.770.140?
RenderCacheFolder nemal pred v2.770.140 žiadny efekt preto, lebo disková vrstva kľúčovala dokumenty na heši zdrojových bajtov, ktorý bežné načítania nikdy nedržali. Dokumentový kľúč pochádzal zo SHA-256 nad internou kópiou surových PDF bajtov, ale LoadFromFile a LoadFromStream parsujú zdroj na mieste a takú kópiu nezanechávajú; pole sa vyplnilo len dočasne na ceste encrypted-recovery a hneď po nej sa zasa vyčistilo. Bez bajtov bol kľúč vždy prázdny a prázdny kľúč znamená, že disková vrstva sa obchádza. Žiadna chyba, žiadne varovanie, len priečinok, ktorý ostával prázdny
Nenulový kľúč odhalil druhý bug schovaný za tým prvým. Staré InvalidateRenderedPageCache mazalo diskový priečinok dokumentu a InvalidateRenderedPageCache beží na začiatku každého načítania, pri každej úprave a vo vnútri Free. V momente, keby kľúč fungoval, by každá relácia prehliadača zničila vlastnú cache pri odchode a ďalšia relácia by aj tak štartovala studená. Horšie, kľúč sa po úprave prepočítaval z rovnakého zdroja, takže rendery upraveného dokumentu by sa ukladali pod kľúčom pôvodného súboru a podávali ďalšej relácii, ktorá otvorí nemodifikované PDF. v2.770.140 opravuje identitu aj invalidáciu spolu; oprava len jednej by dodala buď mŕtvu cache, alebo klamajúcu
Ako HotPDF identifikuje PDF bez čítania celého súboru
HotPDF identifikuje PDF načítané z lokálneho súboru fingerprintom jeho veľkosti, času posledného zápisu a jeho prvých a posledných 64 KiB a stream alebo random-access zdroj identifikuje SHA-256 celého obsahu. Oboje sa zachytí raz, pri úspešnom načítaní a prvých 16 hex znakov SHA-256 digestu (64 bitov) sa stane dokumentovým kľúčom
| Zdroj | Identita | Náklad | Zachytené kedy |
|---|---|---|---|
LoadFromFile | Size + LastWriteTime + úvodných a koncových 64 KiB, hešované SHA-256 | Najviac 128 KiB čítania, nezávislé od veľkosti súboru | Každé úspešné načítanie, dokonca aj keď sa RenderCacheFolder nastaví neskôr |
LoadFromStream | SHA-256 celého streamu | Jeden plný prechod cez zdroj | Len ak bol RenderCacheFolder nastavený pred načítaním |
LoadFromRandomAccessSource | SHA-256 celého zdroja | Jeden plný prechod cez zdroj | Len ak bol priečinok nastavený najprv a celý rozsah je dostupný |
Ľubovoľný zdroj s položkou /Encrypt | Žiadna | Žiadny | Nikdy; disková vrstva sa obchádza |
Súborový fingerprint je zámerný kompromis. Plné hešovanie 400 MB naskenovaného archívu pri každom otvorení môže stáť viac než vyrenderovanie dvoch strán, ktoré si používateľ naozaj pozrie. Vzorkované regióny nie sú ľubovoľné: hlavička sedí na začiatku súboru a trailer aj posledná krížová referenčná sekcia sedia na konci (ISO 32000-1 §7.5). Inkrementálna aktualizácia pripojí nové telo, krížovú referenčnú sekciu aj trailer (§7.5.6), takže mení veľkosť aj chvost naraz. Úplný prepis akýmkoľvek bežným nástrojom mení čas posledného zápisu. Pre súbory do 128 KiB pokrývajú oboje vzorky každý bajt, takže malé dokumenty sa fakticky hešujú celé
Reziduálne riziko je zmena rovnakej veľkosti na mieste do stredu veľkého súboru, ktorého tvorca potom obnoví pôvodnú časovú pečiatku. To vyžaduje nástroj, ktorý zámerne chráni časy modifikácií pri editovaní obsahu, čo je zriedkavé, ale nie nemožné, a v tom prípade cache podáva zastaralé strany. Druhá strana mince je benigná: kopírovanie súboru vo Windows bežne chráni čas posledného zápisu, takže kópia dokumentu už v cache zasiahne tie isté položky, čo je korektné, lebo bajty sú identické
Streamy nemajú čas modifikácie vôbec, takže jediná čestná identita je obsah. HotPDF platí za ten plný prechod SHA-256 len vtedy, keď ste o diskovú cache požiadali pred načítaním; každý iný volajúci LoadFromStream nevidí žiadny extra náklad. Z toho vyplýva, že poradie priradenia property je nosné:
procedure OpenDownloadedPdf(Pdf: THotPDF; Data: TStream;
const CacheRoot: string);
begin
// Pre streamy zlé poradie: obsahový heš sa počíta len vtedy, keď je
// priečinok už nastavený, takže tento dokument by obišiel diskovú vrstvu
// Pdf.LoadFromStream(Data);
// Pdf.RenderCacheFolder := CacheRoot;
Pdf.RenderCacheFolder := CacheRoot; // nastavte najprv
Data.Position := 0;
if Pdf.LoadFromStream(Data) <= 0 then
raise Exception.Create('The stream is not a loadable PDF');
end;
Random-access zdroj, ktorý sa ešte sťahuje (niektoré rozsahy ešte nie sú dostupné), nedostane žiadnu identitu namiesto hešu čiastočného obsahu a ak výpočet identity zlyhá z akéhokoľvek dôvodu, načítanie aj tak uspeje; dokument sa jednoducho renderuje bez diskovej vrstvy
Čo invalíduje položku diskovej cache HotPDF?
Položka diskovej cache HotPDF sa nikdy neinvalíduje zmazaním pri úprave; namiesto toho editovanie načítaného dokumentu upustí identitu dokumentu, takže disková vrstva sa pre zvyšok toho načítania obchádza a uložené strany ostanú platné pre nemodifikovaný zdroj. Položky odchádzajú z disku len cez LRU limity, korumpované PNG alebo zmenu schémy
Kľúč popisuje zdroj na disku, nie objektový graf v pamäti. Akonáhle opečiatkujete stranu alebo zmeníte anotáciu, dokument sa už zhoduje so zdrojom, takže čítanie aj zápis pod jeho kľúčom by boli nesprávne. Od v2.770.140 invalidácia na úrovni dokumentu aj strany čistí identitu namiesto siahania do priečinka a existuje druhá poistka pre úpravy, ktoré nezavolali InvalidateRenderedPageCache: pred použitím diskovej vrstvy THotPDF kontroluje, či je nejaký načítaný objekt dirty a dokument s dirty objektmi berie ako bez identity
Render nastavenia fungujú naopak. Prepnutie PageRenderBackend (alebo volanie UseNativeGDIRenderBackend) a volanie ConfigureRenderICCWorkflow alebo ClearRenderICCWorkflow spláchnu strany v pamäti, ale identitu držia, lebo dokument sa stále zhoduje so svojím zdrojom. Tie nastavenia menia pixely bez toho, aby boli súčasťou variantu v pamäti, takže diskový kľúč zapracúva meno backendu, príznak black-point kompenzácie a SHA-256 digesty ICC proof a output profilov. Samotný variant už pokrýva farebný zámer, výstupný dithering, overprint preview, režim luminosity masky, fallback politiku aj viditeľnosť každej skupiny voliteľného obsahu, takže prepnutie vrstvy renderuje do iného priečinka namiesto prepisu predvoleného pohľadu
Aby sa upravený dokument vrátil na diskovú vrstvu, dajte mu novú zdrojovú identitu uložením a načítaním výsledku:
procedure CommitEditsAndRekey(Pdf: THotPDF; const EditedFile: string);
begin
// Po editovaní načítaného dokumentu: obnovte strany v pamäti.
// Zdrojová identita už je preč, takže sa nič nečíta ani nezapisuje
// do diskového priečinka pôvodného dokumentu
Pdf.InvalidateRenderedPageCache;
// Uložený súbor má novú veľkosť a čas posledného zápisu, teda novú
// identitu; rendery po tomto načítaní sa cachujú pod novým kľúčom
Pdf.SaveLoadedDocument(EditedFile);
if Pdf.LoadFromFile(EditedFile) <= 0 then
raise Exception.Create('Could not reload the edited document');
end;
Priečinok pôvodného dokumentu sa nechá pokoji a zestaráva cez RenderCacheMaxDocuments a RenderCacheMaxBytes ako každá iná položka. Ak používateľ znovu otvorí neupravený originál, jeho strany sú stále tam
Bezpečnostné hranice: šifrované zdroje a prepojené priečinky
Disková render cache HotPDF odmieta dva druhy vstupov zámerne: nikdy nezapisuje strany šifrovaného PDF na disk a nikdy nenasleduje dokumentový podpriečinok, ktorý je junction alebo iný reparse point. Obe pravidlá menia zásahy v cache za to, že nedochádza k úniku dát ani k mazaniu nesprávnych súborov
Šifrované PDF sa nikdy necachujú na disku
Vyrenderovaná strana je dešifrovaný obsah. Zapísanie ako holé PNG do cache priečinka by nechalo čitateľnú kópiu heslom chráneného dokumentu na disku, mimo ochrany, ktorú si zvolil autor (ISO 32000-1 §7.6). HotPDF preto nezachytáva identitu pre žiadny zdroj, ktorého trailer nesie položku /Encrypt, vrátane súborov otvorených s heslom alebo s prázdnym user heslom. Tie dokumenty aj naďalej používajú vrstvu v pamäti, ktorá umiera s procesom
Junction podpriečinky sa odmietajú od v2.770.173
Koreň cache je vaša voľba a nasmerovať ho na junction je dovolené. Dokumentové podpriečinky pod ním sú iná káva: cache ich sama vytvára, číta, dotýka sa ich a maže, počas startup obnovy (ktorá odstraňuje pozostatky dočasných súborov), vyhľadávania (ktoré aktualizuje časové pečiatky), ukladania, invalidácie aj troch vyhadzovacích limitov. Keď niekto s právom zápisu do koreňa cache vymení dokumentový priečinok za junction na iný adresár, každá z tých ciest by ho nasledovala a vyhadzovanie by mazalo súbory niekde, kde cache nikdy nevlastnila nič. Od v2.770.173 každý z tých vstupných bodov kontroluje atribút reparse point a preskočí prepojený dokumentový priečinok: vyhľadávanie počíta minulu, ukladanie počíta zlyhanie zápisu a vyhadzovanie ho nechá pokoje
Unicode cesty a zdieľané korene
Dve súvisiace opravy majú význam, keď nasadzujete do používateľských profilov. Pred v2.770.135 bol RenderCacheFolder AnsiString, takže priečinok mimo systémovej kódovej strany (čínske používateľské meno na anglickej inštalácii Windows napríklad) sa prevádzal so stratami skôr, než ho cache videla; property je teraz Unicode string a atomická náhrada používa wide Windows API. Od v2.770.52 zdieľa viacero inštancií THotPDF v jednom procese ukazujúcich na ten istý koreň (po expanzii cesty, porovnávané case-insensitive) jediný referenciami počítaný index a zámok. Skôr si každá inštancia prepísala index.txt vlastnou kópiou a vynucovala si limity proti svojmu čiastočnému pohľadu, takže priečinok mohol vyrásť niekoľkokrát za rozpočet
Toto zdieľanie sa zastavuje na hranici procesu. Dva oddelené procesy na tom istom koreni stále držia samostatné indexy v pamäti, takže dajte každej súbežne bežiacej aplikácii vlastný koreň cache. Prehliadače renderujúce na worker threadoch sú v poriadku v rámci jedného procesu: PrefetchLoadedPages aj front pokrytý v článku o renderovaní na pozadí s request frontou idú cez rovnakú cachovanú cestu a rovnaký zámok
Rýchla referencia: checklist RenderCacheFolder
- Nastavte
RenderCacheFolder,RenderCacheMaxDocumentsaRenderCacheMaxBytespred prvým volanímRenderLoadedPageToBitmapCached; pre stream a random-access načítania nastavte priečinok pred načítaním - Upgradujte na v2.770.140 alebo novšiu, ak sa spoliehate na diskovú vrstvu; skoršie verzie property prijmú, ale nikdy nepodia stranu z disku pre bežné načítania
- Očakávajte žiadne diskové cachovanie pre šifrované PDF, pre dokumenty upravené po načítaní alebo kým
RenderFallbackPolicynie jerfpIgnore - Uvoľňujte inštanciu THotPDF bežne; od v2.770.140 nemazá diskové položky ani
Free, aniInvalidateRenderedPageCache - Zmena
PageRenderBackendalebo ICC workflow necháva dokument na diskovej vrstve pod iným kľúčom - Používajte jeden koreň cache na bežiacu aplikáciu; inštancie v jednom procese zdieľajú index od v2.770.52
- Držte koreň cache v umiestnení na jedného používateľa; dokumentové podpriečinky, ktoré sú junctions, sa preskakujú od v2.770.173
Perzistentná page cache sa vypláca najviac v prehliadači, ktorý celý deň znovuotvára tie isté dokumenty, presne tvar architektúry vlastného PDF prehliadača v Delphi popísaného inde na tomto blogu. RenderCacheFolder, raster cache v pamäti aj page renderer dodáva HotPDF Delphi PDF component pre Delphi a C++Builder