HotPDF izlaže profil memorije učitanog PDF-a kao graf koji možete ispitivati: BuildLoadedObjectDependencyGraph vraća jedan čvor po neizravnom objektu s procijenjenom plitkom veličinom, zadržanom veličinom temeljenom na dominatoru te zastavicom koja govori je li objekt i dalje dohvatljiv iz kataloga dokumenta. To pretvara „ova datoteka koristi 800 MB” u „objekt 4173, XObject slike, isključivo zadržava 612 MB”, što je činjenica na temelju koje možete djelovati
Razlika između te dvije rečenice cijela je poanta. Plitka veličina govori vam koliko je velik jedan objekt. Zadržana veličina govori vam koliko bi se memorije stvarno oslobodilo kada bi taj objekt nestao, a to je broj koji odlučuje pomaže li popravak ili ne
Zašto ukupna potrošnja memorije nije činjenica na temelju koje se može djelovati?
Zato što u PDF-u gotovo ništa nije u vlasništvu točno jedne stvari. Jedan jedini ugrađeni CID font referenciran je iz rječnika resursa svake stranice koja ga koristi. Tok ICC profila podupire prostor boja koji dijeli deset različitih tokova sadržaja. Form XObject koji se koristi kao pečat pojavljuje se na svih 400 stranica. Ako naivno zbrojite veličine objekata po stranici, taj ćete font izbrojati 400 puta i zaključiti da je svaka stranica golema; ako ga podijelite s 400, zaključit ćete da ništa nije skupo i da je memorija došla odnekud drugdje
Analiza dominatora razrješava tu dvosmislenost na jedini način koji preživi susret sa stvarnim dokumentima. Objekt X pripisuje se najbližem objektu koji ga isključivo zadržava, što znači da svaka referentna putanja od kataloga do X-a prolazi kroz taj dominator. Font koji dijele sve stranice ne pripisuje se nijednoj stranici; pripisuje se najbližem čvoru kroz koji prolaze sve te putanje, a to je obično sam katalog. Font koji koristi točno jedna stranica pripisuje se toj stranici. Rezultat je da se EstimatedRetainedBytes ispravno zbraja umjesto da se duplo broji, a objekti na vrhu popisa objekti su čije bi uklanjanje doista oslobodilo memoriju
Što graf zapravo sadrži
Svaki THPDFObjectDependencyNode nosi identitet objekta kroz ObjectNumber i GenerationNumber, uz ObjectType, LifecycleState, EstimatedShallowBytes, EstimatedRetainedBytes, IncomingReferenceCount, OutgoingReferenceCount, identitet svog neposrednog dominatora te ReachableFromCatalog. Svaki THPDFObjectDependencyEdge bilježi izvor, odredište, Path rječnika pod kojim je referenca pronađena, te je li Resolved
To polje Path ono je koje ljudi nedovoljno koriste. Razlika je između toga da znate kako objekt 91 pokazuje na objekt 4173 i toga da znate kako to čini kroz /Resources/XObject/Im3, što vam odmah govori gledate li sadržaj stranice, tok izgleda napomene ili grupu neobveznog sadržaja koju nitko nikada ne renderira
var
Pdf: THotPDF;
Nodes: THPDFObjectDependencyNodeArray;
Edges: THPDFObjectDependencyEdgeArray;
Info: THPDFObjectDependencyGraphInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('report-800mb.pdf') <> 1 then Exit;
if not Pdf.BuildLoadedObjectDependencyGraph(Nodes, Edges, Info) then Exit;
if Info.LimitExceeded then
Log('Graph truncated: raise MaxObjects / MaxEdges');
Log(Format('%d objects, %d edges, %d reachable, catalog retains %d bytes',
[Info.ObjectCount, Info.EdgeCount, Info.ReachableObjectCount,
Info.CatalogRetainedBytes]));
SortByRetainedDescending(Nodes);
for I := 0 to Min(9, High(Nodes)) do
Log(Format('%d %d obj: shallow %d, retained %d, dominator %d',
[Nodes[I].ObjectNumber, Nodes[I].GenerationNumber,
Nodes[I].EstimatedShallowBytes, Nodes[I].EstimatedRetainedBytes,
Nodes[I].ImmediateDominatorObjectNumber]));
finally
Pdf.Free;
end;
end;
Obje su granice eksplicitni parametri sa zadanim vrijednostima od 250.000 objekata i 2.000.000 rubova. Kada dokument premaši jednu od njih, postavlja se LimitExceeded, a vraćeni graf je skraćeni prefiks, a ne laž: djelomični rezultati uz zastavicu, a ne tiho pogrešni ukupni brojevi. Podignite granice namjerno za forenzičko izvođenje, i imajte na umu da analiza prolazi kroz cijeli graf objekata, pa pripada u dijagnostičku putanju, a ne u vašu petlju renderiranja
Što vam govori nedohvatljiv objekt?
Objekt kod kojeg je ReachableFromCatalog postavljen na False memorija je koju dokument nosi, ali koju preglednik nikada neće prikazati. U praksi dolazi s tri mjesta: inkrementalnih ažuriranja koja su zamijenila raniju verziju objekta i ostavila original iza sebe, proizvođača koji je zapisao objekte koje potom nije uspio povezati, ili oštećene datoteke čija je tablica unakrsnih referenci obnovljena i pokupila definicije koje ništa ne referencira
Prvi je slučaj normalan i očekivan, i upravo je to ono za što su osmišljena inkrementalna ažuriranja i tokovi objekata. Drugi i treći slučaj vrijedi istražiti. Kada velik dio zadržanih bajtova sjedi u nedohvatljivim čvorovima, pronašli ste konkretan argument za ponovno pisanje datoteke umjesto dodavanja na nju, a imate i broj bajtova kojim ćete opravdati dodatno vrijeme obrade pred bilo kim tko pita
Dva brojača alokatora koja vrijedi pročitati prvo
Prije nego što zaključite da je dokument suštinski velik, provjerite je li trošak zapravo sam parser. HotPDF izlaže dva brojača koja opisuju kako se odvijalo učitavanje, a ne što dokument sadrži
GetLastParserArenaStatistics izvještava o zadržanoj areni blokova korištenoj za kratkotrajne tokene parsera i međuspremnike: RetainedBytes, PeakUsedBytes, AllocationCount, ReusedAllocationCount, TokenCount i TemporaryObjectElisionCount. To posljednje polje broji ključeve rječnika parsirane izravno u arenu umjesto kroz privremeni objekt imena, što je alokacija koja je nekoć dominirala parsiranjem datoteka s mnogo rječnika
GetDocumentStringInternStatistics izvještava o internom bazenu na razini dokumenta koji uklanja duplikate ponovljenih PDF imena, operatora tokova sadržaja i nepromjenjivih nizova do 64 bajta. Daje vam RequestCount, HitCount, MissCount, BypassCount, RetainedBytes i ReusedBytes, zajedno s ograničenjima koja su na snazi. Bazen je namjerno ograničen na 65.536 unosa i 4 MiB, tako da zlonamjeran dokument koji generira milijun jedinstvenih imena ne može pretvoriti optimizaciju memorije u pojačalo memorije; jednom kada se dosegne granica, daljnji nizovi zaobilaze bazen, a BypassCount raste
var
Arena: THPDFParserArenaStatistics;
Intern: THPDFDocumentStringInternStatistics;
begin
if Pdf.GetLastParserArenaStatistics(Arena) then
Log(Format('arena: peak %d, reused %d of %d allocations, %d tokens',
[Arena.PeakUsedBytes, Arena.ReusedAllocationCount,
Arena.AllocationCount, Arena.TokenCount]));
if Pdf.GetDocumentStringInternStatistics(Intern) then
Log(Format('intern: %d/%d hits, %d bypassed, %d bytes reused',
[Intern.HitCount, Intern.RequestCount, Intern.BypassCount,
Intern.ReusedBytes]));
end;
Visoka vrijednost ReusedBytes uz nizak BypassCount znači da dokument ima ponavljajući vokabular kakav imaju gotovo svi stvarni PDF-ovi i da se bazen isplati. BypassCount koji se približava RequestCount-u znači nešto neuobičajeno: ili je riječ o istinski golemom dokumentu, ili o dokumentu koji namjerno generira jedinstvena imena, što je blagi signal vrijedan bilježenja na putanji prihvata nepouzdanih datoteka
Redoslijed trijaže koji funkcionira
Krenite s CatalogRetainedBytes iz informacija grafa. Ako je taj broj blizu rasta vašeg procesa, memorija je u dokumentu i graf će vam pokazati gdje. Ako je znatno niži, memorija je u vašim vlastitim predmemorijama, u renderiranim bitmapama ili u parseru, a brojači arene reći će vam koje od toga
Zatim uzmite prvih deset čvorova prema EstimatedRetainedBytes i pogledajte njihov ObjectType. Image XObjecti na vrhu znače da je datoteka bogata skeniranim materijalom i da je downsampling rješenje. Deskriptori fontova na vrhu znače da su ugrađeni cijeli fontovi ondje gdje bi bili dovoljni podskupovi. Tokovi sadržaja na vrhu obično znače generiranu vektorsku grafiku, često karte ili CAD izvoze. Tek nakon toga isplati se pogledati nedohvatljive objekte i ponašanje alokatora. Radeći tim redoslijedom, odgovor ćete obično pronaći u prva dva koraka, a za vrlo velike dokumente pristup strujanja opisan u članku tijek rada s Direct File API-jem često je strukturno rješenje umjesto bilo kakve optimizacije po pojedinom objektu
Sve su ove dijagnostike obični Pascal pozivi koji vraćaju obične zapise, pa se izravno uklapaju u postojeću putanju bilježenja ili telemetrije. HotPDF je izvorna VCL PDF komponenta za Delphi i C++Builder s potpunim izvornim kodom; API referenca i probna verzija dostupne su na stranici HotPDF Delphi PDF komponente