تعد معالجة ملفات PDF القياسية (من 1 ميجابايت إلى 10 ميجابايت) في Delphi أمرًا مباشرًا باستخدام فئات الدفق القياسية مثل TFileStream أو TMemoryStream. ومع ذلك، عندما يتم تكليفك بمعالجة ملفات PDF بحجم الجيجابايت، مثل المخططات الهندسية الضخمة لـ CAD، أو الخرائط الجغرافية المكانية عالية الدقة، أو الأرشيفات القانونية المتراكمة، فإن تقنيات تخصيص الذاكرة القياسية تتعطل بسرعة.
إذا قمت بتحميل ملف PDF بحجم 2 جيجابايت في TMemoryStream داخل تطبيق Delphi بتنسيق 32 بت، فستواجه فورًا استثناء EOutOfMemory. وحتى في تطبيقات 64 بت، يؤدي القيام بذلك إلى حدوث أخطاء صفحات شديدة (page faulting) ويؤدي إلى توقف الخادم تمامًا. في هذه المقالة، سنستكشف كيفية تحسين أداء الإدخال/الإخراج (I/O) للملفات الضخمة باستخدام الملفات المعينة في الذاكرة (Memory-Mapped Files).
المشكلة في التدفقات القياسية
عندما تستخدم TMemoryStream.LoadFromFile، يقرأ نظام التشغيل الملف من القرص، ويخصص ذاكرة وصول عشوائي (RAM) متسلسلة، وينسخ البيانات إليها. بالنسبة لملف بحجم 2 جيجابايت، يؤدي هذا إلى إهدار 2 جيجابايت من ذاكرة الوصول العشوائي الفعلية ويستغرق وقتًا طويلاً فقط لحلقة قراءة القرص.
حتى استخدام TFileStream يمكن أن يكون مشكلة إذا كنت تنتقل داخل الملف بشكل متكرر (على سبيل المثال، تحليل جدول XRef الخاص بـ PDF في نهاية الملف، ثم القفز إلى كائنات متناثرة في جميع أنحاء الملف). تؤدي استدعاءات Seek و Read المستمرة إلى عبء كبير في انتقال النواة (kernel transition).
الحل: الملفات المعينة في الذاكرة
يطلب تعيين الذاكرة (عبر وظائف واجهة برمجة تطبيقات Windows CreateFileMapping و MapViewOfFile) من نظام التشغيل تعيين الملف مباشرة في مساحة العنوان الافتراضية للتطبيق. تحصل على مؤشر للبيانات، ويتعامل مدير الذاكرة الافتراضية في Windows مع ترحيل البيانات داخل وخارج ذاكرة الوصول العشوائي الفعلية بشكل صارم عند وصولك إليها.
إليك كيفية تنفيذ قارئ ملفات معين في الذاكرة عالي الأداء في Delphi لتحليل PDF:
uses
Winapi.Windows, System.SysUtils, System.Classes;
type
TMemoryMappedFileReader = class
private
FFileHandle: THandle;
FMappingHandle: THandle;
FDataPtr: Pointer;
FFileSize: Int64;
public
constructor Create(const FileName: string);
destructor Destroy; override;
property Data: Pointer read FDataPtr;
property Size: Int64 read FFileSize;
end;
constructor TMemoryMappedFileReader.Create(const FileName: string);
var
HighSize, LowSize: DWORD;
begin
// Open the file with read permissions
FFileHandle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if FFileHandle = INVALID_HANDLE_VALUE then
RaiseLastOSError;
// Get the 64-bit file size
LowSize := GetFileSize(FFileHandle, @HighSize);
FFileSize := (Int64(HighSize) shl 32) or LowSize;
// Create the mapping object
FMappingHandle := CreateFileMapping(FFileHandle, nil, PAGE_READONLY, HighSize, LowSize, nil);
if FMappingHandle = 0 then
RaiseLastOSError;
// Map the file into the virtual address space
FDataPtr := MapViewOfFile(FMappingHandle, FILE_MAP_READ, 0, 0, 0);
if FDataPtr = nil then
RaiseLastOSError;
end;
destructor TMemoryMappedFileReader.Destroy;
begin
if FDataPtr <> nil then UnmapViewOfFile(FDataPtr);
if FMappingHandle <> 0 then CloseHandle(FMappingHandle);
if FFileHandle <> INVALID_HANDLE_VALUE then CloseHandle(FFileHandle);
inherited;
end;
لماذا يهيمن تعيين الذاكرة على تحليل PDF
يعد تنسيق PDF تنسيق وصول عشوائي. يبدأ المحلل بقراءة المقطع الدعائي في نهاية الملف، ويجد جدول XRef، ثم يقفز عشوائيًا إلى إزاحات البايت في جميع أنحاء الملف لتحميل قواميس وتيارات محددة.
مع تعيين الذاكرة:
- نسخ صفري (Zero-Copy): لا يتم نسخ البيانات من مساحة النواة إلى مساحة المستخدم؛ بل تقرأ مباشرة من ذاكرة التخزين المؤقت لملفات نظام التشغيل.
- تحميل فوري: يستغرق فتح ملف PDF بحجم 2 جيجابايت أجزاء من الثانية، حيث لا تتم قراءة أي بيانات فعليًا من القرص حتى تقوم بإلغاء مرجعية المؤشر (dereference the pointer).
- ترحيل يديره نظام التشغيل: إذا قمت بتحليل 50 ميجابايت فقط من البيانات من ملف بحجم 2 جيجابايت، فلن يقوم نظام التشغيل بتحميل سوى هذه الـ 50 ميجابايت في ذاكرة الوصول العشوائي الفعلية. يبقى استهلاك الذاكرة ضئيلًا جدًا.
من خلال تنفيذ فئة دفق مخصصة مدعومة بملفات معينة في الذاكرة، يمكن لتطبيق Delphi الخاص بك معالجة ملفات PDF بحجم الجيجابايت بسهولة، مما يؤدي إلى تحسين الأداء وقابلية التوسع بشكل كبير.
ملاحظة: تم دمج معالجة تدفق الإدخال/الإخراج المحسنة للمستندات الضخمة مباشرة في مكون HotPDF VCL.