HotPDF eksponerer hukommelsesprofilen for en indlæst PDF som en graf, du kan forespørge: BuildLoadedObjectDependencyGraph returnerer én node pr. indirekte objekt med en estimeret uforgrenet størrelse, en dominator-baseret fastholdt størrelse og et flag for, om objektet stadig kan nås fra dokumentets Catalog. Det gør "denne fil bruger 800 MB" til "objekt 4173, et billed-XObject, fastholder eksklusivt 612 MB", hvilket er en kendsgerning, du kan handle på
Forskellen mellem de to sætninger er hele pointen. Uforgrenet størrelse fortæller dig, hvor stort ét objekt er. Fastholdt størrelse fortæller dig, hvor meget hukommelse der faktisk ville blive frigivet, hvis det objekt forsvandt, hvilket er det tal, der afgør, om en rettelse hjælper
Hvorfor er totalt hukommelsesforbrug ikke en handlingsbar kendsgerning?
Fordi der i en PDF næsten intet er ejet af præcis én ting. En enkelt indlejret CID-skrifttype refereres fra ressourceordbogen for hver side, der bruger den. En ICC-profilstrøm understøtter et farverum, som ti forskellige indholdsstrømme deler. Et Form-XObject brugt som et stempel optræder på alle 400 sider. Hvis du naivt lægger objektstørrelser sammen pr. side, tæller du den skrifttype 400 gange og konkluderer, at hver side er enorm; hvis du dividerer det med 400, konkluderer du, at intet er dyrt, og at hukommelsen kom fra et andet sted
Dominatoranalyse løser tvetydigheden på den eneste måde, der overlever kontakt med rigtige dokumenter. Et objekt X tilskrives det nærmeste objekt, der eksklusivt fastholder det, hvilket betyder, at hver referencesti fra Catalog til X passerer gennem den dominator. En skrifttype, delt af alle sider, tilskrives ingen side; den tilskrives den nærmeste node, som alle de stier passerer igennem, hvilket normalt er selve Catalog'et. En skrifttype, brugt af præcis én side, tilskrives den side. Resultatet er, at EstimatedRetainedBytes summerer korrekt i stedet for at dobbelttælle, og objekterne øverst på listen er objekter, hvis fjernelse reelt ville frigive hukommelse
Hvad indeholder grafen egentlig?
Hver THPDFObjectDependencyNode bærer objektidentiteten som ObjectNumber og GenerationNumber, plus ObjectType, LifecycleState, EstimatedShallowBytes, EstimatedRetainedBytes, IncomingReferenceCount, OutgoingReferenceCount, identiteten på dens umiddelbare dominator og ReachableFromCatalog. Hver THPDFObjectDependencyEdge registrerer kilde, mål, ordbogsstien Path, referencen blev fundet under, og om den Resolved
Det Path-felt er det, folk underbruger. Det er forskellen mellem at vide, at objekt 91 peger på objekt 4173, og at vide, at det gør det gennem /Resources/XObject/Im3, hvilket øjeblikkeligt fortæller dig, om du kigger på sideindhold, en annotationsudseendestrøm eller en valgfri-indholdsgruppe, ingen nogensinde gengiver
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;
Begge grænser er eksplicitte parametre med standardværdier på 250.000 objekter og 2.000.000 kanter. Når et dokument overskrider en af dem, sættes LimitExceeded, og den returnerede graf er et afkortet præfiks frem for en løgn: delvise resultater med et flag, ikke lydløst forkerte totaler. Hæv grænserne bevidst til en retsmedicinsk kørsel, og husk, at analysen gennemløber hele objektgrafen, så den hører til i en diagnostisk sti, ikke i din render-løkke
Hvad fortæller et uopnåeligt objekt dig?
Et objekt med ReachableFromCatalog sat til False er hukommelse, dokumentet bærer, men som viseren aldrig vil vise. I praksis kommer det fra tre steder: inkrementelle opdateringer, der har erstattet en tidligere version af et objekt og efterladt originalen, en producent, der skrev objekter, den derefter undlod at linke, eller en beskadiget fil, hvis krydsreferencetabel er blevet genopbygget og har fanget definitioner, som intet refererer til
Det første tilfælde er normalt og forventet, og det er præcis det, inkrementelle opdateringer og objektstrømme er designet til. Det andet og tredje er værd at undersøge. Når en stor del af de fastholdte bytes ligger i uopnåelige noder, har du fundet et konkret argument for at genskrive filen frem for at tilføje til den, og du har bytetallet til at retfærdiggøre den ekstra behandlingstid over for, hvem der spørger
To allokatortællere, det er værd at læse først
Før du konkluderer, at et dokument er iboende stort, skal du tjekke, om selve parseren er omkostningen. HotPDF eksponerer to tællere, der beskriver, hvordan indlæsningen forløb, frem for hvad dokumentet indeholder
GetLastParserArenaStatistics rapporterer den fastholdte blok-arena, brugt til kortlivede parser-tokens og buffere: RetainedBytes, PeakUsedBytes, AllocationCount, ReusedAllocationCount, TokenCount og TemporaryObjectElisionCount. Det sidste felt tæller ordbogsnøgler, parset direkte ind i arenaen frem for gennem et midlertidigt navneobjekt, hvilket er den allokering, der plejede at dominere parsing af ordbogstunge filer
GetDocumentStringInternStatistics rapporterer den dokument-afgrænsede intern-pulje, der deduplikerer gentagne PDF-navne, indholdsstrøm-operatorer og uforanderlige strenge op til 64 byte. Den giver dig RequestCount, HitCount, MissCount, BypassCount, RetainedBytes og ReusedBytes, sammen med de gældende lofter. Puljen er bevidst afgrænset til 65.536 poster og 4 MiB, så et fjendtligt dokument, der genererer en million unikke navne, ikke kan gøre en hukommelsesoptimering til en hukommelsesforstærker; når loftet er nået, springer yderligere strenge puljen over, og BypassCount stiger
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;
En høj ReusedBytes-værdi med et lavt BypassCount betyder, at dokumentet har det gentagne ordforråd, de fleste rigtige PDF'er har, og at puljen tjener sit ophold. Et BypassCount, der nærmer sig RequestCount, betyder noget usædvanligt: enten et reelt enormt dokument, eller et, der genererer unikke navne med vilje, hvilket er et mildt signal, det er værd at logge på en ubetroet indtagelsessti
En triageorden, der virker
Start med CatalogRetainedBytes fra grafinfoen. Hvis det tal er tæt på din procesvækst, er hukommelsen i dokumentet, og grafen vil vise dig hvor. Hvis det er langt under, er hukommelsen i dine egne caches, i gengivne bitmaps eller i parseren, og arena-tællerne vil sige hvilket
Tag derefter de ti øverste noder efter EstimatedRetainedBytes, og se på deres ObjectType. Billed-XObjects øverst betyder, at filen er scan-tung, og at nedskalering er løsningen. Skrifttypebeskrivelser øverst betyder, at fulde skrifttyper blev indlejret, hvor subsets ville have gjort det. Indholdsstrømme øverst betyder normalt genereret vektorgrafik, ofte kort eller CAD-eksporter. Først efter det betaler det sig at se på uopnåelige objekter og allokatoradfærd. Ved at arbejde i den rækkefølge finder du normalt svaret i de første to trin, og for meget store dokumenter er den streamende tilgang, beskrevet i den direkte fil-API-workflow, ofte den strukturelle løsning frem for nogen per-objekt-optimering
Alle disse diagnostikker er almindelige Pascal-kald, der returnerer almindelige records, så de indsættes direkte i en eksisterende log- eller telemetristi. HotPDF er en native VCL-PDF-komponent til Delphi og C++Builder med fuld kildekode; API-referencen og en trial-build findes på HotPDF's produktside for Delphi PDF-komponenten