Обробка стандартних PDF-файлів (від 1 МБ до 10 МБ) у Delphi є простою завдяки використанню стандартних класів потоків, таких як TFileStream або TMemoryStream. Однак, коли перед вами стоїть завдання обробки гігабайтних PDF-файлів, наприклад масивних інженерних схем САПР, геопросторових карт з високою роздільною здатністю або накопичених юридичних архівів, стандартні методи розподілу пам'яті швидко перестають працювати.
Якщо ви завантажите PDF-файл розміром 2 ГБ у TMemoryStream у 32-розрядному додатку Delphi, ви негайно отримаєте виняток EOutOfMemory. Навіть у 64-розрядних додатках це призводить до серйозних помилок сторінок пам'яті та зупиняє роботу сервера. У цій статті ми розглянемо, як оптимізувати продуктивність вводу-виводу для масивних файлів за допомогою відображення файлів у пам'ять (Memory-Mapped Files).
Проблема зі стандартними потоками
Коли ви використовуєте TMemoryStream.LoadFromFile, операційна система зчитує файл з диска, виділяє послідовну оперативну пам'ять і копіює в неї дані. Для файлу розміром 2 ГБ це марнує 2 ГБ фізичної оперативної пам'яті і займає значний час лише на цикл читання з диска.
Навіть використання TFileStream може бути проблематичним, якщо ви часто переміщуєтеся по файлу (наприклад, аналізуєте таблицю PDF XRef в кінці файлу, а потім переходите до об'єктів, розкиданих по всьому файлу). Постійні виклики Seek і Read призводять до високих накладних витрат на перехід до ядра.
Рішення: Відображення файлів у пам'ять (Memory-Mapped Files)
Відображення в пам'ять (за допомогою функцій Windows API 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 ГБ займає мілісекунди, оскільки фактично жодні дані не зчитуються з диска, доки ви не розіменуєте покажчик.
- Керування сторінками (Paging) за допомогою ОС: Якщо ви аналізуєте лише 50 МБ даних з файлу розміром 2 ГБ, ОС завантажує лише ці 50 МБ у фізичну оперативну пам'ять. Споживання пам'яті залишається крихітним.
Впровадивши власний клас потоку, що підтримується файлами, відображеними в пам'ять, ваша програма Delphi зможе з легкістю перетравлювати гігабайтні PDF-файли, значно покращуючи продуктивність і масштабованість.
Примітка: Оптимізована обробка потоків вводу-виводу для масивних документів вбудована безпосередньо в компонент HotPDF VCL.