Technical Article

Gigabayt Ölçekli PDF İşleme İçin IO Performansını Optimize Etme

Delphi'de standart PDF'leri (1MB ila 10MB) işlemek, TFileStream veya TMemoryStream gibi standart akış (stream) sınıfları kullanılarak oldukça basittir. Ancak, devasa mühendislik CAD şemaları, yüksek çözünürlüklü coğrafi haritalar veya birikmiş yasal arşivler gibi gigabayt ölçekli PDF'leri işlemekle görevlendirildiğinizde, standart bellek ayırma teknikleri hızla çöker.

32-bit bir Delphi uygulamasında 2GB'lık bir PDF'i bir TMemoryStream içine yüklerseniz, anında bir EOutOfMemory istisnasıyla karşılaşırsınız. 64-bit uygulamalarda bile, bunu yapmak ciddi sayfa hatalarına (page faulting) neden olur ve sunucuyu durma noktasına getirir. Bu makalede, Bellek Eşlemeli Dosyalar (Memory-Mapped Files) kullanarak devasa dosyalar için I/O performansını nasıl optimize edebileceğimizi keşfedeceğiz.

Standart Akışların (Streams) Sorunu

TMemoryStream.LoadFromFile kullandığınızda, işletim sistemi (OS) dosyayı diskten okur, sıralı RAM ayırır ve verileri buraya kopyalar. 2GB'lık bir dosya için bu, 2GB'lık fiziksel RAM'i israf eder ve sadece disk okuma döngüsü için önemli bir zaman harcar.

Dosya içinde sık sık atlama yapıyorsanız (örneğin, dosyanın sonundaki PDF XRef tablosunu ayrıştırıp, ardından dosya boyunca dağılmış nesnelere atlıyorsanız) TFileStream kullanmak bile sorunlu olabilir. Sürekli Seek ve Read çağrıları, yüksek çekirdek (kernel) geçiş yüküne neden olur.

Çözüm: Bellek Eşlemeli Dosyalar

Bellek eşleme (Windows API işlevleri olan CreateFileMapping ve MapViewOfFile aracılığıyla), işletim sisteminden dosyayı doğrudan uygulamanın sanal adres alanına eşlemesini ister. Verilere bir işaretçi (pointer) alırsınız ve Windows Sanal Bellek Yöneticisi (Virtual Memory Manager), verilere yalnızca eriştiğinizde onları fiziksel RAM'e sayfalamayı (paging in and out) halleder.

Delphi'de PDF ayrıştırma için yüksek performanslı bir bellek eşlemeli dosya okuyucusunu nasıl uygulayabileceğiniz aşağıda açıklanmıştır:

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 Ayrıştırmada Bellek Eşleme Neden Üstündür?

PDF, rastgele erişimli (random-access) bir formattır. Ayrıştırıcı, dosyanın sonundaki kuyruğu (trailer) okuyarak başlar, XRef tablosunu bulur ve ardından belirli sözlükleri ve akışları yüklemek için dosya boyunca rastgele bayt ofsetlerine atlar.

Bellek eşleme ile:

  1. Sıfır Kopya (Zero-Copy): Veriler çekirdek alanından (kernel space) kullanıcı alanına (user space) kopyalanmaz; doğrudan işletim sistemi dosya önbelleğinden okursunuz.
  2. Anında Yükleme: İşaretçiyi kuralsızlaştırmadan (dereference) diskten gerçekten hiçbir veri okunmadığı için 2GB'lık bir PDF'i açmak milisaniyeler sürer.
  3. İşletim Sistemi Yönetimli Sayfalama: 2GB'lık dosyanın yalnızca 50MB'lık verisini ayrıştırırsanız, işletim sistemi yalnızca o 50MB'ı fiziksel RAM'e yükler. Bellek tüketimi çok küçük kalır.

Bellek eşlemeli dosyalarla desteklenen özel bir akış sınıfı uygulayarak, Delphi uygulamanız gigabayt ölçekli PDF'leri kolayca işleyebilir, performansı ve ölçeklenebilirliği önemli ölçüde artırabilir.

Not: Devasa belgeler için optimize edilmiş I/O akış (stream) yönetimi doğrudan HotPDF VCL Component bileşenine entegre edilmiştir.