HotPDF RenderCacheFolder pretvara predmemoriju renderiranih stranica u memoriji HotPDF Delphi komponente u trajnu diskovnu predmemoriju stranica: renderirane stranice zapisuju se kao PNG datoteke u mapu koju odaberete, a sljedeći put kad se isti PDF izvor otvori, RenderLoadedPageToBitmapCached čita ih natrag umjesto da ponovno rasterizira. Redoslijed pretraživanja jest memorija, pa disk, pa renderer
Diskovna razina bila je u API-ju od v2.416.0, ali do v2.770.140 nikad zapravo nije poslužila stranicu za običan poziv LoadFromFile ili LoadFromStream. Popravak je nametnuo pitanje na koje svaka trajna predmemorija mora odgovoriti: odakle znate da je datoteka koju ste danas otvorili dokument koji ste jučer renderirali, i što se događa s predmemoriranim stranicama kad nije? Dolje su odgovori na koje se HotPDF smirio, uključujući gdje namjerno odbija predmemorirati
Kako radi HotPDF diskovna render predmemorija?
HotPDF diskovna render predmemorija druga je razina iza raster predmemorije u memoriji i sudjeluje samo kad je RenderCacheFolder neprazna putanja. Poziv RenderLoadedPageToBitmapCached(PageIndex, DPI) najprije pregledava unose u memoriji, s ključem po indeksu stranice, DPI-ju i varijanti render postavki. Na promašaj pita diskovnu razinu; diskovni pogodak dekodira PNG, vraća ga u memoriju i daje kopiju u vlasništvu pozivatelja. Tek kad obje razine promaše stranica prolazi kroz interpreter content streama opisan u renderiranju učitane PDF stranice u TBitmap, a svježa se bitmapa tada zapisuje i na disk
Na disku raspored je namjerno dosadan. Svaki dokument dobiva podmapu imenovanu iz 16-znakovnog heksadecimalnog ključa dokumenta plus 16-znakovne heksadecimalne render varijante, svaka se stranica sprema kao <page>@<dpi>.png, a index.txt u korijenu drži dokumente u redoslijedu nedavno korištenih iza schema oznake. Nepoklapanje schemata čisti mapu pri prvoj upotrebi. Upisi idu najprije u privremenu datoteku i zamjenjuju se na mjesto atomičkom zamjenom, pa pad usred upisa ostavlja ili staru stranicu ili ništa, nikad pola PNG-a. PNG koji ne uspije dekodirati briše se i broji kao promašaj
Tri granice omeđuju mapu:
RenderCacheMaxDocuments(zadano 20) ograničava broj podmapa dokumenata; najdavnije korištena mapa izbacuje se prvaRenderCacheMaxBytes(zadano 524288000, što je 500 MB) ograničava ukupnu veličinu svih PNG datoteka pod korijenom- Svaka mapa dokumenta drži najviše 200 slikovnih stranica; to ograničenje po dokumentu fiksira THotPDF i nije objavljeno svojstvo
RenderCacheCapacity (zadano 8) zaseban je regulator: postavlja koliko renderiranih stranica razina u memoriji drži, i nema nikakve veze s diskovnim otiskom
uses
SysUtils, Graphics, HPDFDoc;
procedure WarmThumbnails(const FileName: string);
var
Pdf: THotPDF;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
// Podesite diskovnu razinu prije prvog predmemoriranog rendera:
// mapa i obje granice čitaju se kad se razina prvi put koristi
Pdf.RenderCacheFolder := IncludeTrailingPathDelimiter(
GetEnvironmentVariable('LOCALAPPDATA')) + 'MyViewer\PageCache';
Pdf.RenderCacheMaxDocuments := 50;
Pdf.RenderCacheMaxBytes := Int64(1024) * 1024 * 1024; // 1 GiB
Pdf.RenderCacheCapacity := 16; // stranice u memoriji
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
// Predajte kopiju traci sličica ovdje
finally
Bmp.Free; // predmemorirani poziv uvijek vraća kopiju u vlasništvu pozivatelja
end;
end;
finally
Pdf.Free; // od v2.770.140 ovo više ne briše diskovne unose
end;
end;
Pokrenite istu proceduru dvaput i drugo izvođenje nikad ne rasterizira stranicu koja je stala u predmemoriju. Objekt diskovne predmemorije stvara se lijenom pri prvom predmemoriranom renderu i živi dok se THotPDF instanca ne oslobodi, pa promjena RenderCacheFolder, RenderCacheMaxDocuments ili RenderCacheMaxBytes nakon toga ne premješta ni ne mijenja veličinu već otvorene predmemorije. Stranice prevelike za politiku primanja u memoriji (po zadanim vrijednostima jedan unos ne smije prijeći 64 MiB 32-bitnih piksela) također se ne spremaju trajno, a diskovna se razina konzultira samo dok RenderFallbackPolicy drži svoju zadanu rfpIgnore, jer fallback dijagnostika ne sprema se uz PNG
Zašto RenderCacheFolder nikad nije radio prije v2.770.140?
RenderCacheFolder nije imao učinka prije v2.770.140 jer je diskovna razina ključala dokumente hashom izvornih bajtova koje obična učitavanja nikad nisu zadržavala. Ključ dokumenta dolazio je iz SHA-256 nad internom kopijom sirovih PDF bajtova, ali LoadFromFile i LoadFromStream parsiraju izvor na mjestu i ne zadržavaju takvu kopiju; polje je popunjavano samo privremeno na putanji oporavka od šifriranja i odmah poslije ponovno čistjeno. Bez bajtova ključ je uvijek bio prazan, a prazan ključ znači da se diskovna razina zaobilazi. Bez pogreške, bez upozorenja, samo mapa koja je ostala prazna
Činjenica da ključ nije prazan otkrila je drugi bug koji se krio iza prvoga. Stari InvalidateRenderedPageCache brisao je diskovnu mapu dokumenta, a InvalidateRenderedPageCache izvodi se na početku svakog učitavanja, pri svakoj izmjeni i unutar Free. Pa bi onog trenutka kad bi ključ proradio svaka sesija preglednika uništila vlastitu predmemoriju pri izlasku, i sljedeća bi sesija ionako počela hladna. Gore od toga, ključ je ponovno računan iz istog izvora nakon izmjene, pa bi renderi uređenog dokumenta bili spremljeni pod ključem izvorne datoteke i posluženi sljedećoj sesiji koja otvori neizmijenjeni PDF. v2.770.140 popravlja identitet i poništavanje zajedno; popraviti samo jedno isporučilo bi ili mrtvu predmemoriju ili lažnu
Kako HotPDF identificira PDF bez čitanja cijele datoteke
HotPDF identificira PDF učitan iz lokalne datoteke otiskom njegove veličine, vremena posljednjeg zapisa te njegovih prvih i posljednjih 64 KiB, a tok ili random-access izvor identificira SHA-256 njegovog cijelog sadržaja. Oboje se bilježi jednom, kad učitavanje uspije, i prvih 16 heksadecimalnih znakova SHA-256 sažetka (64 bita) postaje ključ dokumenta
| Izvor | Identitet | Trošak | Bilježi se kad |
|---|---|---|---|
LoadFromFile | Veličina + LastWriteTime + vodećih i repnih 64 KiB, hashirano SHA-256 | Najviše 128 KiB čitanja, neovisno o veličini datoteke | Svako uspješno učitavanje, i ako se RenderCacheFolder postavi kasnije |
LoadFromStream | SHA-256 cijelog toka | Jedan potpuni prolaz kroz izvor | Samo ako je RenderCacheFolder bio postavljen prije učitavanja |
LoadFromRandomAccessSource | SHA-256 cijelog izvora | Jedan potpuni prolaz kroz izvor | Samo ako je mapa bila postavljena prva i cijeli raspon je dostupan |
Svaki izvor s /Encrypt unosom | Nijedan | Nijedan | Nikad; diskovna se razina zaobilazi |
Otisak datoteke namjerna je razmjena. Hashiranje 400 MB skeniranog arhiva u cijelosti pri svakom otvaranju može koštati više nego renderiranje dviju stranica koje korisnik stvarno gleda. Uzorkovana područja nisu proizvoljna: zaglavlje sjedi na početku datoteke, a trailer i posljednja cross-reference sekcija na kraju (ISO 32000-1 §7.5). Inkrementalna nadogradnja dodaje novo tijelo, cross-reference sekciju i trailer (§7.5.6), pa mijenja veličinu i rep u jednom. Potpuni prepis bilo kojim normalnim alatom mijenja vrijeme posljednjeg zapisa. Za datoteke do 128 KiB dva uzorka pokrivaju svaki bajt, pa su mali dokumenti djelotvorno hashirani u cijelosti
Preostali rizik jest promjena iste veličine na mjestu usred velike datoteke čiji pisac zatim vrati izvornu vremensku oznaku. To treba alat koji namjerno čuva vremena izmjena dok uređuje sadržaj, što je retko ali ne nemoguće, i u tom slučaju predmemorija poslužuje zastarjele stranice. Obrnuta strana je benigna: kopiranje datoteke na Windowsima obično čuva vrijeme posljednjeg zapisa, pa kopija dokumenta koji je već u predmemoriji pogađa iste unose, što je ispravno jer su bajtovi identični
Tokovi uopće nemaju vrijeme izmjene, pa je jedini pošteni identitet sadržaj. HotPDF plaća taj potpuni SHA-256 prolaz samo ako ste diskovnu predmemoriju zatražili prije učitavanja; svaki drugi pozivatelj LoadFromStream ne vidi dodatni trošak. To redoslijed dodjele svojstava čini nosivim:
procedure OpenDownloadedPdf(Pdf: THotPDF; Data: TStream;
const CacheRoot: string);
begin
// Pogrešan redoslijed za tokove: hash sadržaja računa se samo kad je
// mapa već postavljena, pa bi ovaj dokument zaobišao diskovnu razinu
// Pdf.LoadFromStream(Data);
// Pdf.RenderCacheFolder := CacheRoot;
Pdf.RenderCacheFolder := CacheRoot; // postavi prvi
Data.Position := 0;
if Pdf.LoadFromStream(Data) <= 0 then
raise Exception.Create('The stream is not a loadable PDF');
end;
Random-access izvor koji se još preuzima (neki rasponi još nisu dostupni) ne dobiva identitet, a ne hash djelomičnog sadržaja, i ako računanje identiteta padne iz bilo kojeg razloga učitavanje i dalje uspijeva; dokument jednostavno renderira bez diskovne razine
Što poništava unos HotPDF diskovne predmemorije?
HotPDF diskovni predmemorinski unos nikad se ne poništava brisanjem pri izmjeni; umjesto toga, uređivanje učitanog dokumenta ispada identitet dokumenta, pa se diskovna razina zaobilazi do kraja tog učitavanja, a spremljene stranice ostaju valjane za neizmijenjeni izvor. Unosi napuštaju disk samo kroz LRU i bajtne granice, pokvareni PNG ili promjenu schemata
Ključ opisuje izvor na disku, a ne graf objekata u memoriji. Jednom kad poštampite stranicu ili promijenite anotaciju, dokument se više ne poklapa s tim izvorom, pa ni čitanje ni pisanje pod njegovim ključem ne bi bilo ispravno. Od v2.770.140 i poništavanje na razini dokumenta i na razini stranice čisti identitet umjesto da dira mapu, i postoji drugi čuvar za izmjene koje nisu pozvale InvalidateRenderedPageCache: prije upotrebe diskovne razine THotPDF provjerava je li bilo koji učitani objekt prljav i tretira prljav dokument kao da nema identiteta
Render postavke rade obrnuto. Prebacivanje PageRenderBackend (ili poziv UseNativeGDIRenderBackend), i pozivanje ConfigureRenderICCWorkflow ili ClearRenderICCWorkflow, ispuštaju stranice u memoriji ali zadržavaju identitet, jer se dokument i dalje poklapa sa svojim izvorom. Te postavke mijenjaju piksele ne čineći dio varijante u memoriji, pa diskovni ključ upliće ime backenda, zastavicu black-point kompenzacije i SHA-256 sažetke ICC proof i izlaznih profila. Varijanta sama već pokriva namjeru boja, izlazno dithering, overprint pregled, način luminosity maske, fallback politiku i vidljivost svake neobavezne content grupe, pa prebacivanje sloja renderira u drugu mapu umjesto da prepiše zadani prikaz
Da vratite uređeni dokument na diskovnu razinu, dajte mu novi identitet izvora spremanjem i učitavanjem rezultata:
procedure CommitEditsAndRekey(Pdf: THotPDF; const EditedFile: string);
begin
// Nakon uređivanja učitanog dokumenta: osvježite stranice u memoriji.
// Identitet izvora već je nestao, pa se ništa ne čita iz niti
// piše u diskovnu mapu izvornog dokumenta
Pdf.InvalidateRenderedPageCache;
// Spremljena datoteka ima novu veličinu i vrijeme posljednjeg zapisa, dakle novi
// identitet; renderi nakon ovog učitavanja predmemoriraju se pod novim ključem
Pdf.SaveLoadedDocument(EditedFile);
if Pdf.LoadFromFile(EditedFile) <= 0 then
raise Exception.Create('Could not reload the edited document');
end;
Mapa izvornog dokumenta ostavljena je na miru i stari kroz RenderCacheMaxDocuments i RenderCacheMaxBytes poput bilo kojeg drugog unosa. Ako korisnik ponovno otvori neuređeni original, njegove su stranice još tamo
Sigurnosne granice: šifrirani izvori i povezane mape
HotPDF diskovna render predmemorija odbija dvije vrste ulaza namjerno: nikad ne zapisuje stranice šifriranog PDF-a na disk i nikad ne slijedi podmapu dokumenta koja je junction ili drugi reparse point. Oba pravila trguju pogocima predmemorije za necurenje podataka ili brisanje pogrešnih datoteka
Šifrirani PDF-ovi nikad se ne predmemoriraju na disku
Renderirana stranica dešifriran je sadržaj. Zapisivanje kao običan PNG u mapu predmemorije ostavilo bi čitljivu kopiju dokumenta zaštićenog lozinkom na disku, izvan zaštite koju je autor odabrao (ISO 32000-1 §7.6). HotPDF stoga ne bilježi identitet za nijedan izvor čiji trailer nosi /Encrypt unos, uključujući datoteke otvorene s lozinkom ili s praznom korisničkom lozinkom. Ti dokumenti i dalje koriste razinu u memoriji, koja umire s procesom
Junction podmape odbijaju se od v2.770.173
Korijen predmemorije vaš je izbor, i usmjeravanje na junction dopušteno je. Podmape dokumenata ispod njega druga su priča: predmemorija ih sama stvara, čita, dodiruje i briše, tijekom oporavka pri pokretanju (koji uklanja zaostale privremene datoteke), pretraživanja (koje ažurira vremenske oznake), spremanja, poništavanja i tri granice izbacivanja. Da netko s pravom pisanja na korijen predmemorije zamijeni mapu dokumenta junctionom na drugi direktorij, svaka bi od tih putanja slijedila njega, i izbacivanje bi brisalo datoteke negdje gdje predmemorija nikad nije vladala. Od v2.770.173 svaka od tih ulaznih točaka provjerava reparse-point atribut i preskače povezanu mapu dokumenta: pretraživanje broji promašaj, spremanje broji neuspjeh upisa, a izbacivanje je ostavlja na miru
Unicode putanje i dijeljeni korijeni
Dva povezana popravka važna su ako se raspoređujete na korisničke profile. Prije v2.770.135 RenderCacheFolder bio je AnsiString, pa je mapa izvan sistemske kodne stranice (kinesko korisničko ime na engleskoj Windows instalaciji, na primjer) gubivo pretvorena prije nego ju je predmemorija vidjela; svojstvo je sada Unicode string, a atomička zamjena koristi široki Windows API. Od v2.770.52 nekoliko THotPDF instanci u jednom procesu koje pokazuju na isti korijen (nakon proširenja putanje, uspoređeno bez razlikovanja velikih i malih slova) dijele jedan indeks s brojanjem referenci i jedan lock. Ranije je svaka instanca prepravljala index.txt svojom kopijom i provodila granice nad svojim djelomičnim prikazom, pa je mapa mogla narasti nekoliko puta preko proračuna
To dijeljenje staje na granici procesa. Dva odvojena procesa na istom korijenu i dalje drže odvojene indekse u memoriji, pa dajte svakoj istovremeno pokrenutoj aplikaciji vlastiti korijen predmemorije. Preglednici koji renderiraju na worker dretvama u redu su unutar jednog procesa: PrefetchLoadedPages i red opisan u pozadinskom renderiranju s redom zahtjeva oboje prolaze kroz isti predmemorirani put i isti lock
Brza referenca: RenderCacheFolder kontrolna lista
- Postavite
RenderCacheFolder,RenderCacheMaxDocumentsiRenderCacheMaxBytesprije prvog pozivaRenderLoadedPageToBitmapCached; za učitavanja tokova i random-access postavite mapu prije učitavanja - Nadogradite na v2.770.140 ili kasniji ako se oslanjate na diskovnu razinu; ranije verzije prihvaćaju svojstvo ali nikad ne poslužuju stranicu s diska za obična učitavanja
- Očekujte bez diskovnog predmemoriranja za šifrirane PDF-ove, za dokumente uređene nakon učitavanja, ili dok
RenderFallbackPolicynijerfpIgnore - Oslobodite THotPDF instancu normalno; od v2.770.140 ni
FreeniInvalidateRenderedPageCachene brišu diskovne unose - Promjena
PageRenderBackendili ICC workflowa zadržava dokument na diskovnoj razini pod drugim ključem - Koristite jedan korijen predmemorije po pokrenutoj aplikaciji; instance unutar jednog procesa dijele indeks od v2.770.52
- Držite korijen predmemorije na lokaciji po korisniku; podmape dokumenata koje su junctioni preskaču se od v2.770.173
Trajna predmemorija stranica najviše se isplati u pregledniku koji cijeli dan ponovno otvara iste dokumente, što je točno oblik arhitekture prilagođenog PDF preglednika u Delphiju opisanog drugdje na ovom blogu. RenderCacheFolder, raster predmemorija u memoriji i renderer stranica isporučuju se s HotPDF Delphi PDF komponentom za Delphi i C++Builder