Technical Article

การเพิ่มประสิทธิภาพ IO สำหรับการประมวลผล PDF ขนาดกิกะไบต์

การประมวลผล PDF มาตรฐาน (1MB ถึง 10MB) ใน Delphi นั้นทำได้อย่างตรงไปตรงมาโดยใช้คลาสสตรีมมาตรฐาน เช่น TFileStream หรือ TMemoryStream อย่างไรก็ตาม เมื่อคุณได้รับมอบหมายให้ประมวลผล PDF ขนาดกิกะไบต์ เช่น แผนผัง CAD วิศวกรรมขนาดใหญ่ แผนที่เชิงพื้นที่ความละเอียดสูง หรือคลังเก็บเอกสารทางกฎหมายที่สะสมไว้ เทคนิคการจัดสรรหน่วยความจำมาตรฐานจะล้มเหลวอย่างรวดเร็ว

หากคุณโหลด PDF ขนาด 2GB ลงใน TMemoryStream ในแอปพลิเคชัน Delphi แบบ 32 บิต คุณจะพบกับข้อยกเว้น EOutOfMemory ทันที แม้แต่ในแอปพลิเคชันแบบ 64 บิต การทำเช่นนั้นก็ทำให้เกิด page faulting อย่างรุนแรงและทำให้เซิร์ฟเวอร์หยุดชะงัก ในบทความนี้ เราจะสำรวจวิธีการเพิ่มประสิทธิภาพ I/O สำหรับไฟล์ขนาดใหญ่โดยใช้ Memory-Mapped Files (ไฟล์ที่แมปกับหน่วยความจำ)

ปัญหาของ Standard Streams (สตรีมมาตรฐาน)

เมื่อคุณใช้ TMemoryStream.LoadFromFile ระบบปฏิบัติการจะอ่านไฟล์จากดิสก์ จัดสรร RAM ตามลำดับ และคัดลอกข้อมูลลงไป สำหรับไฟล์ขนาด 2GB สิ่งนี้จะสิ้นเปลือง RAM จริงไป 2GB และใช้เวลาอย่างมากเพียงเพื่อวงรอบการอ่านดิสก์

แม้แต่การใช้ TFileStream ก็อาจเป็นปัญหาได้หากคุณต้องกระโดดข้ามไปมาภายในไฟล์บ่อยๆ (เช่น การแยกวิเคราะห์ตาราง PDF XRef ที่ส่วนท้ายของไฟล์ จากนั้นกระโดดไปยังออบเจ็กต์ที่กระจัดกระจายอยู่ทั่วทั้งไฟล์) การเรียก Seek และ Read อย่างต่อเนื่องส่งผลให้เกิดโอเวอร์เฮดของการเปลี่ยนผ่านเคอร์เนลที่สูง

วิธีแก้ปัญหา: Memory-Mapped Files

การแมปหน่วยความจำ (ผ่านฟังก์ชัน Windows API CreateFileMapping และ MapViewOfFile) เป็นการขอให้ระบบปฏิบัติการแมปไฟล์ลงในพื้นที่ที่อยู่เสมือนของแอปพลิเคชันโดยตรง คุณจะได้รับพอยน์เตอร์ไปยังข้อมูล และ Virtual Memory Manager ของ Windows จะจัดการการทำเพจจิ้งข้อมูลเข้าและออกจาก RAM จริงอย่างเคร่งครัดตามที่คุณเข้าถึงมัน

นี่คือวิธีที่คุณสามารถนำตัวอ่านไฟล์ที่แมปกับหน่วยความจำประสิทธิภาพสูงไปใช้ใน 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 เป็นรูปแบบการเข้าถึงแบบสุ่ม ตัวแยกวิเคราะห์เริ่มต้นด้วยการอ่าน trailer ที่ส่วนท้ายของไฟล์ ค้นหาตาราง XRef จากนั้นสุ่มกระโดดไปยังออฟเซ็ตไบต์ต่างๆ ทั่วทั้งไฟล์เพื่อโหลดพจนานุกรมและสตรีมเฉพาะ

ด้วยการแมปหน่วยความจำ:

  1. Zero-Copy: ข้อมูลจะไม่ถูกคัดลอกจากพื้นที่เคอร์เนลไปยังพื้นที่ผู้ใช้ คุณอ่านได้โดยตรงจากแคชไฟล์ของระบบปฏิบัติการ
  2. โหลดได้ทันที: การเปิด PDF ขนาด 2GB ใช้เวลาเพียงเสี้ยววินาที เนื่องจากจะไม่มีการอ่านข้อมูลจากดิสก์จริงๆ จนกว่าคุณจะดีเรเฟอเรนซ์พอยน์เตอร์
  3. OS จัดการการทำเพจจิ้ง (OS Managed Paging): หากคุณแยกวิเคราะห์ข้อมูลเพียง 50MB จากไฟล์ขนาด 2GB ระบบปฏิบัติการจะโหลดแค่ 50MB นั้นลงใน RAM จริง การใช้หน่วยความจำยังคงมีขนาดเล็กมาก

ด้วยการนำคลาสสตรีมแบบกำหนดเองที่สนับสนุนโดยไฟล์ที่แมปกับหน่วยความจำมาใช้ แอปพลิเคชัน Delphi ของคุณจะสามารถประมวลผล PDF ขนาดกิกะไบต์ได้อย่างง่ายดาย ช่วยปรับปรุงประสิทธิภาพและความสามารถในการขยายขนาดได้อย่างมาก

หมายเหตุ: การจัดการสตรีม I/O ที่ปรับให้เหมาะสมสำหรับเอกสารขนาดใหญ่ถูกสร้างขึ้นโดยตรงใน HotPDF VCL Component