Teknik Makale

HotPDF: Nesne Bağımlılık Grafikleriyle PDF Bellek Yiyicileri

HotPDF, yüklü bir PDF'in bellek profilini sorgulayabileceğiniz bir grafik olarak açar: BuildLoadedObjectDependencyGraph, dolaylı nesne başına bir düğüm döndürür; her düğüm tahmini bir sığ boyut, baskın temelli bir tutulan boyut ve nesnenin hâlâ belge Catalog'undan erişilebilir olup olmadığını gösteren bir bayrak taşır. Bu, "bu dosya 800 MB kullanıyor" ifadesini "nesne 4173, bir görüntü XObject'i, 612 MB'ı münhasıran tutuyor" ifadesine dönüştürür ki bu, üzerinde hareket edebileceğiniz bir bilgidir

Bu iki cümle arasındaki fark, konunun tamamıdır. Sığ boyut, bir nesnenin ne kadar büyük olduğunu söyler. Tutulan boyut ise o nesne yok olsaydı gerçekte ne kadar belleğin serbest kalacağını söyler ki bir düzeltmenin işe yarayıp yaramayacağına karar veren sayı da budur

Toplam bellek kullanımı neden üzerinde hareket edilebilir bir bilgi değildir?

Çünkü bir PDF'te hemen hemen hiçbir şey tam olarak tek bir şeye ait değildir. Tek bir gömülü CID yazı tipine, onu kullanan her sayfanın kaynak sözlüğünden başvurulur. Bir ICC profil akışı, on farklı içerik akışının paylaştığı bir renk uzayını destekler. Damga olarak kullanılan bir Form XObject'i 400 sayfanın tümünde görünür. Nesne boyutlarını sayfa başına saf bir şekilde toplarsanız, o yazı tipini 400 kez saymış ve her sayfanın devasa olduğu sonucuna varmış olursunuz; 400'e bölerseniz de hiçbir şeyin pahalı olmadığı ve belleğin başka bir yerden geldiği sonucuna varırsınız

Baskın analizi, belirsizliği gerçek belgelerle temas ettiğinde hayatta kalan tek şekilde çözer. Bir X nesnesi, en yakın kendisini münhasıran tutan nesneye atfedilir; yani Catalog'dan X'e giden her başvuru yolu o baskından geçer. Tüm sayfaların paylaştığı bir yazı tipi hiçbir sayfaya atfedilmez; o yolların tümünün geçtiği en yakın düğüme atfedilir ki bu genellikle Catalog'un kendisidir. Yalnızca bir sayfanın kullandığı bir yazı tipi ise o sayfaya atfedilir. Sonuç olarak EstimatedRetainedBytes çift saymak yerine doğru toplanır ve listenin başındaki nesneler, kaldırılması gerçekten belleği boşaltacak nesnelerdir

Grafik gerçekte neler içerir?

Her THPDFObjectDependencyNode, nesne kimliğini ObjectNumber ve GenerationNumber olarak taşır; ayrıca ObjectType, LifecycleState, EstimatedShallowBytes, EstimatedRetainedBytes, IncomingReferenceCount, OutgoingReferenceCount, doğrudan baskının kimliğini ve ReachableFromCatalog'u taşır. Her THPDFObjectDependencyEdge ise kaynağı, hedefi, başvurunun bulunduğu sözlük Path'ini ve Resolved olup olmadığını kaydeder

O Path alanı, insanların yeterince kullanmadığı alandır. Nesne 91'in nesne 4173'e işaret ettiğini bilmekle bunun /Resources/XObject/Im3 üzerinden olduğunu bilmek arasındaki fark, sayfa içeriğine mi, bir açıklama görünüm akışına mı yoksa kimsenin hiç oluşturmadığı isteğe bağlı bir içerik grubuna mı baktığınızı hemen size söyler

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;

Her iki sınır da varsayılan olarak 250.000 nesne ve 2.000.000 kenar olan açık parametrelerdir. Bir belge bu sınırlardan birini aştığında LimitExceeded ayarlanır ve döndürülen grafik bir yalan değil kırpılmış bir önektir: bir bayrakla birlikte kısmi sonuçlar, sessizce yanlış toplamlar değil. Adli bir çalıştırma için sınırları bilinçli olarak yükseltin ve analizin tüm nesne grafiğini dolaştığını unutmayın; bu yüzden yerinin oluşturma döngünüz değil bir tanılama yolu olduğunu hatırlayın

Erişilemeyen bir nesne size ne söyler?

ReachableFromCatalog'u False olan bir nesne, belgenin taşıdığı ama görüntüleyicinin asla göstermeyeceği bellektir. Pratikte üç yerden gelir: bir nesnenin önceki bir sürümünü geçersiz kılan ve orijinalini geride bırakan artımlı güncellemeler, yazıp sonra bağlayamayan bir üretici ya da çapraz referans tablosu yeniden inşa edilmiş ve kimsenin başvurmadığı tanımları almış hasarlı bir dosya

İlk durum normaldir ve beklenendir; tam olarak artımlı güncellemeler ve nesne akışlarının yapmak için tasarlandığı şeydir. İkincisi ve üçüncüsü araştırılmaya değer. Tutulan baytların büyük bir kısmı erişilemeyen düğümlerde oturduğunda, dosyayı eklemek yerine yeniden yazmak için somut bir gerekçe bulmuş olursunuz ve soran kişiye ekstra işlem süresini haklı çıkaracak bayt sayısına sahip olursunuz

Önce okunmaya değer iki tahsisçi sayacı

Bir belgenin doğası gereği büyük olduğu sonucuna varmadan önce, ayrıştırıcının kendisinin maliyet olup olmadığını kontrol edin. HotPDF, belgenin içerdiği şeyi değil yüklemenin nasıl davrandığını anlatan iki sayaç açar

GetLastParserArenaStatistics, kısa ömürlü ayrıştırıcı belirteçleri ve arabellekleri için kullanılan tutulan blok arenasını raporlar: RetainedBytes, PeakUsedBytes, AllocationCount, ReusedAllocationCount, TokenCount ve TemporaryObjectElisionCount. Bu sonuncu alan, bir geçici ad nesnesi yerine doğrudan arenaya ayrıştırılan sözlük anahtarlarını sayar; bu, sözlük ağırlıklı dosyaların ayrıştırılmasına eskiden hakim olan tahsisti

GetDocumentStringInternStatistics, tekrarlanan PDF adlarını, içerik akışı operatörlerini ve 64 bayta kadar değişmez dizeleri tekilleştiren belge kapsamlı içerleştirme havuzunu raporlar. Size RequestCount, HitCount, MissCount, BypassCount, RetainedBytes ve ReusedBytes'ı, yürürlükteki tavanlarla birlikte verir. Havuz bilinçli olarak 65.536 girdi ve 4 MiB ile sınırlanmıştır; bu yüzden bir milyon benzersiz ad üreten kötü niyetli bir belge, bir bellek optimizasyonunu bir bellek yükseltecine çeviremez. Tavana ulaşıldığında, sonraki dizeler havuzu atlar ve BypassCount yükselir

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;

Düşük bir BypassCount ile yüksek bir ReusedBytes değeri, belgenin çoğu gerçek PDF'in sahip olduğu tekrarlayan kelime dağarcığına sahip olduğu ve havuzun karşılığını verdiği anlamına gelir. RequestCount'a yaklaşan bir BypassCount ise sıra dışı bir şeyi gösterir: ya gerçekten devasa bir belge ya da bilinçli olarak benzersiz adlar üreten bir belge; bu, güvenilmeyen bir alım yolunda günlüğe kaydetmeye değer hafif bir sinyaldir

İşe yarayan bir sıralama düzeni

Grafik bilgisinden gelen CatalogRetainedBytes ile başlayın. Bu sayı süreç büyümenize yakınsa bellek belgededir ve grafik size nerede olduğunu gösterecektir. Çok altındaysa bellek kendi önbelleklerinizde, oluşturulmuş bit eşlemlerde ya da ayrıştırıcıdadır ve arena sayaçları hangisi olduğunu söyleyecektir

Ardından EstimatedRetainedBytes'a göre ilk on düğümü alın ve ObjectType'larına bakın. Zirvede görüntü XObject'leri, dosyanın tarama ağırlıklı olduğunu ve düşük örneklemenin çözüm olduğunu gösterir. Zirvede yazı tipi tanımlayıcıları, alt kümelerin yeterli olacağı yerde tam yüzlerin gömüldüğünü gösterir. Zirvede içerik akışları genellikle üretilmiş vektör grafikleri anlamına gelir; çoğunlukla haritalar ya da CAD dışa aktarımları. Ancak bundan sonra erişilemeyen nesnelere ve tahsisçi davranışına bakmak işe yarar. Bu sırayla çalışırsanız yanıtı genellikle ilk iki adımda bulursunuz ve çok büyük belgeler için doğrudan dosya API'si iş akışında anlatılan akış yaklaşımı sık sık nesne başına herhangi bir optimizasyondan çok yapısal bir düzeltmedir

Bu tanıların tümü, mevcut bir günlükleme ya da telemetri yoluna doğrudan yerleşen sade Pascal çağrıları olarak sade kayıtlar döndürür. HotPDF, tam kaynak koduna sahip Delphi ve C++Builder için yerel bir VCL PDF bileşenidir; API referansı ve bir deneme sürümü HotPDF Delphi PDF bileşeni sayfasında bulunur