يعرض HotPDF ملف الذاكرة لمستند PDF محمَّل كرسم بياني يمكنك استعلامه: تُعيد BuildLoadedObjectDependencyGraph عقدة واحدة لكل كائن غير مباشر مع حجم سطحي مقدَّر، وحجم محتفَظ به مبني على العقدة المسيطرة، وعلامة تشير إلى ما إذا كان الكائن لا يزال يمكن الوصول إليه من فهرس المستند. هذا يحوّل عبارة «هذا الملف يستخدم 800 ميغابايت» إلى «الكائن 4173، وهو XObject صورة، يحتفظ حصرياً بـ 612 ميغابايت»، وهي حقيقة يمكنك التصرف بناءً عليها
الفرق بين هاتين الجملتين هو بيت القصيد بأكمله. الحجم السطحي يخبرك بحجم كائن واحد. الحجم المحتفَظ به يخبرك بمقدار الذاكرة التي ستُحرَّر فعلياً لو زال ذلك الكائن، وهو الرقم الذي يحدد ما إذا كان الإصلاح سيفيد
لماذا لا يكون إجمالي استخدام الذاكرة حقيقة قابلة للتصرف؟
لأنه في PDF لا يمتلك شيءٌ تقريباً شيئاً واحداً بالضبط. خط CID مضمَّن واحد يُشار إليه من قاموس الموارد لكل صفحة تستخدمه. تيار ملف تعريف ICC يدعم مساحة لونية تتشاركها عشرة تيارات محتوى مختلفة. Form XObject يُستخدم كختم يظهر في كل الصفحات الأربعمئة. إذا جمعت أحجام الكائنات لكل صفحة بسذاجة، فستحسب ذلك الخط 400 مرة وتستنتج أن كل صفحة ضخمة؛ وإذا قسمته على 400، فستستنتج أن لا شيء مكلف وأن الذاكرة جاءت من مكان آخر
يحل تحليل العقدة المسيطرة الغموض بالطريقة الوحيدة التي تصمد أمام مستندات حقيقية. يُعزى الكائن X إلى أقرب كائن يحتفظ به حصرياً، بمعنى أن كل مسار مرجعي من الفهرس إلى X يمر عبر تلك العقدة المسيطرة. الخط المشترك بين كل الصفحات لا يُعزى إلى أي صفحة؛ بل يُعزى إلى أقرب عقدة تمر عبرها كل تلك المسارات، وهي عادة الفهرس نفسه. الخط المستخدم في صفحة واحدة بالضبط يُعزى إلى تلك الصفحة. النتيجة هي أن EstimatedRetainedBytes يُجمع بشكل صحيح بدلاً من الحساب المزدوج، والكائنات في أعلى القائمة هي كائنات ستؤدي إزالتها فعلاً إلى تحرير الذاكرة
ما الذي يحتويه الرسم البياني فعلياً
تحمل كل عقدة THPDFObjectDependencyNode هوية الكائن كـ ObjectNumber وGenerationNumber، بالإضافة إلى ObjectType وLifecycleState وEstimatedShallowBytes وEstimatedRetainedBytes وIncomingReferenceCount وOutgoingReferenceCount، وهوية عقدتها المسيطرة المباشرة، وReachableFromCatalog. تسجّل كل حافة THPDFObjectDependencyEdge المصدر والهدف، وPath القاموس الذي وُجد المرجع تحته، وما إذا كانت Resolved
حقل Path هذا هو الذي يقلّل الناس من استخدامه. إنه الفرق بين معرفة أن الكائن 91 يشير إلى الكائن 4173 ومعرفة أنه يفعل ذلك عبر /Resources/XObject/Im3، وهو ما يخبرك فوراً ما إذا كنت تنظر إلى محتوى صفحة، أو تيار مظهر تعليق توضيحي، أو مجموعة محتوى اختياري لا يعرضها أحد أبداً
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;
كلا الحدين معاملان صريحان بقيم افتراضية تبلغ 250,000 كائن و2,000,000 حافة. عندما يتجاوز مستند أياً منهما، تُضبط LimitExceeded ويكون الرسم البياني المُعاد بادئة مقتطَعة وليس كذبة: نتائج جزئية مع علامة، وليست مجاميع خاطئة بصمت. ارفع الحدود عمداً لتشغيل جنائي، وتذكّر أن التحليل يمشي عبر رسم الكائنات بأكمله، لذا فمكانه في مسار تشخيصي، لا في حلقة العرض الخاصة بك
ماذا يخبرك كائن لا يمكن الوصول إليه؟
الكائن الذي تكون ReachableFromCatalog فيه مضبوطة على False هو ذاكرة يحملها المستند لكن العارض لن يعرضها أبداً. عملياً يأتي هذا من ثلاثة أماكن: تحديثات تزايدية استبدلت نسخة سابقة من كائن وتركت الأصل خلفها، أو مولّد كتب كائنات ثم فشل في ربطها، أو ملف تالف أُعيد بناء جدول مراجعه المتقاطعة والتقط تعريفات لا يشير إليها شيء
الحالة الأولى طبيعية ومتوقعة، وهي بالضبط ما صُمِّمت من أجله التحديثات التزايدية وتيارات الكائنات. الحالتان الثانية والثالثة تستحقان التحقيق. عندما يجلس جزء كبير من البايتات المحتفظ بها في عقد لا يمكن الوصول إليها، تكون قد وجدت حجة ملموسة لإعادة كتابة الملف بدلاً من الإلحاق به، ولديك عدد البايتات لتبرير وقت المعالجة الإضافي لأي شخص يسأل
عدادا مخصِّص يستحقان القراءة أولاً
قبل أن تستنتج أن مستنداً ما كبير بطبيعته، تحقق مما إذا كان المحلل نفسه هو التكلفة. يعرض HotPDF عدادين يصفان كيفية سلوك التحميل بدلاً من محتوى المستند
تُبلغ GetLastParserArenaStatistics عن حيّز الكتل المحتفَظ به المستخدم لرموز ومخازن المحلل قصيرة العمر: RetainedBytes وPeakUsedBytes وAllocationCount وReusedAllocationCount وTokenCount وTemporaryObjectElisionCount. يُحصي هذا الحقل الأخير مفاتيح القاموس المحلَّلة مباشرة داخل الحيّز بدلاً من عبر كائن اسم مؤقت، وهي عملية التخصيص التي كانت تهيمن على تحليل الملفات كثيفة القواميس
تُبلغ GetDocumentStringInternStatistics عن مجمع التدوين المحصور بنطاق المستند الذي يزيل التكرار من أسماء PDF المتكررة، ومُشغِّلات تيار المحتوى، والسلاسل غير القابلة للتغيير حتى 64 بايت. يمنحك RequestCount وHitCount وMissCount وBypassCount وRetainedBytes وReusedBytes، إلى جانب الحدود القصوى المطبَّقة. المجمع محدود عمداً عند 65,536 إدخالاً و4 ميبيبايت، بحيث لا يستطيع مستند معادٍ يولّد مليون اسم فريد تحويل تحسين للذاكرة إلى مضخِّم للذاكرة؛ وعند بلوغ الحد الأقصى، تتجاوز السلاسل الإضافية المجمع ويرتفع BypassCount
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;
قيمة عالية لـ ReusedBytes مع BypassCount منخفض تعني أن المستند يملك المفردات المتكررة التي تملكها معظم ملفات PDF الحقيقية وأن المجمع يثبت جدواه. اقتراب BypassCount من RequestCount يعني شيئاً غير معتاد: إما مستند ضخم فعلاً، أو مستند يولّد أسماء فريدة عمداً، وهي إشارة خفيفة تستحق التسجيل في مسار استيعاب غير موثوق
ترتيب فرز يعمل فعلياً
ابدأ بـ CatalogRetainedBytes من معلومات الرسم البياني. إذا كان هذا الرقم قريباً من نمو عمليتك، فإن الذاكرة موجودة في المستند وسيريك الرسم البياني مكانها. إذا كان أقل بكثير، فإن الذاكرة موجودة في مخابئك الخاصة، أو في الصور النقطية المعروضة، أو في المحلل، وستخبرك عدادات الحيّز أيهما
بعد ذلك خذ العقد العشر الأولى بحسب EstimatedRetainedBytes وانظر إلى ObjectType الخاص بها. وجود XObjects للصور في القمة يعني أن الملف كثيف المسح الضوئي وأن تقليل العينات هو الحل. وجود واصفات خطوط في القمة يعني أن خطوطاً كاملة أُضمِّنت حيث كانت المجموعات الفرعية كافية. وجود تيارات محتوى في القمة يعني عادة رسومات متجهة مولَّدة، غالباً خرائط أو تصديرات CAD. لا يستحق النظر إلى الكائنات غير القابلة للوصول وسلوك المخصِّص إلا بعد ذلك. بالعمل بهذا الترتيب، ستجد الإجابة عادة في الخطوتين الأوليين، وبالنسبة للمستندات الكبيرة جداً فإن نهج التدفق الموضح في سير عمل واجهة الملف المباشرة غالباً ما يكون الإصلاح البنيوي بدلاً من أي تحسين لكل كائن على حدة
كل هذه التشخيصات عبارة عن استدعاءات Pascal بسيطة تُعيد سجلات بسيطة، بحيث تندمج مباشرة في مسار تسجيل أو قياس عن بُعد قائم. HotPDF هو مكون VCL PDF أصلي لـ Delphi وC++Builder مع الشيفرة المصدرية الكاملة؛ ومرجع واجهة البرمجة ونسخة تجريبية متاحان على صفحة مكون HotPDF لـ Delphi