HotPDF eksponerer minneprofilen til en innlastet PDF som en graf du kan spørre: BuildLoadedObjectDependencyGraph returnerer én node per indirekte objekt med en estimert grunn størrelse, en dominatorbasert holdt størrelse, og et flagg for om objektet fortsatt er nåbart fra dokumentkatalogen. Det gjør «denne filen bruker 800 MB» om til «objekt 4173, et bilde-XObject, holder eksklusivt på 612 MB», som er et faktum du kan handle på
Forskjellen mellom de to setningene er hele poenget. Grunn størrelse forteller deg hvor stort ett objekt er. Holdt størrelse forteller deg hvor mye minne som faktisk ville blitt frigjort dersom det objektet forsvant, og det er tallet som avgjør om en fiks hjelper
Hvorfor er total minnebruk ikke et handlingsrettet faktum?
Fordi nesten ingenting i en PDF eies av bare én ting. En enkelt innebygd CID-skrift refereres fra ressursordboken til hver side som bruker den. En ICC-profilstrøm ligger bak en fargeromsdefinisjon som ti forskjellige innholdsstrømmer deler. Et Form XObject brukt som et stempel dukker opp på alle 400 sidene. Hvis du naivt legger sammen objektstørrelser per side, teller du den skriften 400 ganger og konkluderer med at hver side er enorm; hvis du deler på 400, konkluderer du med at ingenting er kostbart og at minnet kom fra et annet sted
Dominatoranalyse løser opp tvetydigheten på den eneste måten som overlever kontakt med reelle dokumenter. Et objekt X tilskrives det nærmeste objektet som eksklusivt holder på det, som betyr at hver referansesti fra katalogen til X går gjennom den dominatoren. En skrift delt av alle sider tilskrives ingen enkelt side; den tilskrives den nærmeste noden alle disse stiene går gjennom, som vanligvis er selve katalogen. En skrift brukt av nøyaktig én side tilskrives den siden. Resultatet er at EstimatedRetainedBytes summeres korrekt i stedet for å dobbelttelles, og objektene øverst på listen er objekter der fjerning genuint ville frigjort minne
Hva grafen faktisk inneholder
Hver THPDFObjectDependencyNode bærer objektidentiteten som ObjectNumber og GenerationNumber, pluss ObjectType, LifecycleState, EstimatedShallowBytes, EstimatedRetainedBytes, IncomingReferenceCount, OutgoingReferenceCount, identiteten til sin umiddelbare dominator, og ReachableFromCatalog. Hver THPDFObjectDependencyEdge registrerer kilde, mål, ordbokstien Path referansen ble funnet under, og om den Resolved
Feltet Path er det folk underbruker. Det er forskjellen mellom å vite at objekt 91 peker på objekt 4173, og å vite at det gjør det gjennom /Resources/XObject/Im3, som umiddelbart forteller deg om du ser på sideinnhold, en annotasjonsutseendestrøm, eller en valgfri innholdsgruppe ingen noensinne rendrer
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 grensene er eksplisitte parametere med standardverdier på 250 000 objekter og 2 000 000 kanter. Når et dokument overskrider en av dem, settes LimitExceeded, og den returnerte grafen er et avkuttet prefiks fremfor en løgn: delvise resultater med et flagg, ikke stille feilaktige totaler. Hev grensene bevisst for en rettsmedisinsk kjøring, og husk at analysen går gjennom hele objektgrafen, så den hører hjemme i en diagnostisk vei, ikke i rendersløyfen din
Hva forteller et uoppnåelig objekt deg?
Et objekt med ReachableFromCatalog satt til False er minne dokumentet bærer, men som viseren aldri vil vise. I praksis kommer det fra tre steder: inkrementelle oppdateringer som erstattet en tidligere versjon av et objekt og lot originalen bli liggende igjen, en produsent som skrev objekter den deretter ikke klarte å lenke, eller en skadet fil hvis kryssreferansetabell ble gjenoppbygd og fanget opp definisjoner som ingenting refererer til
Det første tilfellet er normalt og forventet, og det er nøyaktig det inkrementelle oppdateringer og objektstrømmer er designet for å gjøre. Det andre og tredje er verdt å undersøke. Når en stor andel av de holdte bytene ligger i uoppnåelige noder, har du funnet et konkret argument for å skrive om filen i stedet for å legge til den, og du har byteantallet som trengs for å rettferdiggjøre den ekstra prosesseringstiden overfor den som spør
To allokatortellere verdt å lese først
Før du konkluderer med at et dokument iboende er stort, sjekk om selve parseren er kostnaden. HotPDF eksponerer to tellere som beskriver hvordan innlastingen oppførte seg, fremfor hva dokumentet inneholder
GetLastParserArenaStatistics rapporterer den holdte blokkarenaen brukt for korttidsparser-tokener og buffere: RetainedBytes, PeakUsedBytes, AllocationCount, ReusedAllocationCount, TokenCount og TemporaryObjectElisionCount. Det siste feltet teller ordboknøkler analysert direkte inn i arenaen i stedet for gjennom et midlertidig navneobjekt, som er allokeringen som tidligere dominerte analysen av ordbok-tunge filer
GetDocumentStringInternStatistics rapporterer den dokumentbundne interneringspoolen som deduplikerer gjentatte PDF-navn, operatorer i innholdsstrømmer og uforanderlige strenger opp til 64 byte. Den gir deg RequestCount, HitCount, MissCount, BypassCount, RetainedBytes og ReusedBytes, sammen med de gjeldende takene. Poolen er bevisst begrenset til 65 536 oppføringer og 4 MiB, slik at et fiendtlig dokument som genererer en million unike navn, ikke kan gjøre en minneoptimalisering om til en minneforsterker; når taket er nådd, går videre strenger utenom poolen 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øy ReusedBytes-verdi med en lav BypassCount betyr at dokumentet har det repetitive vokabularet de fleste reelle PDF-er har, og at poolen gjør nytte for seg. En BypassCount som nærmer seg RequestCount, betyr noe uvanlig: enten et genuint enormt dokument, eller ett som genererer unike navn med hensikt, noe som er et mildt signal verdt å logge på en utiltrodd mottaksvei
En prioriteringsrekkefølge som virker
Start med CatalogRetainedBytes fra grafinfoen. Hvis det tallet er nær prosessveksten din, ligger minnet i dokumentet, og grafen vil vise deg hvor. Hvis det er langt under, ligger minnet i dine egne cacher, i rendrede bitmaps, eller i parseren, og arenatellerne vil fortelle hvilket
Ta deretter de ti øverste nodene etter EstimatedRetainedBytes og se på ObjectType deres. Bilde-XObjects øverst betyr at filen er skann-tung og at nedskalering er fiksen. Skriftbeskrivelser øverst betyr at fulle skrifter ble bygget inn der subsett hadde holdt. Innholdsstrømmer øverst betyr vanligvis genererte vektorgrafikker, ofte kart eller CAD-eksporter. Først etter det lønner det seg å se på uoppnåelige objekter og allokatoratferd. Ved å jobbe i den rekkefølgen finner du som regel svaret i de to første trinnene, og for svært store dokumenter er strømmetilnærmingen beskrevet i den direkte fil-API-arbeidsflyten ofte den strukturelle fiksen fremfor en per-objekt-optimalisering
Alle disse diagnostikkene er vanlige Pascal-kall som returnerer vanlige records, så de faller rett inn i en eksisterende logg- eller telemetrivei. HotPDF er en native VCL PDF-komponent for Delphi og C++Builder med full kildekode; API-referansen og en prøvebygg finnes på HotPDF sin side for Delphi PDF-komponent