benchmark load/save ที่ซื่อสัตย์สำหรับ PDF Library for Delphi จับเวลา LoadFromFile กับ SaveToFile ด้วย QueryPerformanceCounter เก็บ raw tick กับความถี่ของ counter ไว้ รัน baseline กับ candidate สลับกันเป็นคู่ A/B, B/A, A/B ปฏิเสธเริ่มงานตรงที่ load ของ CPU เกิน 25% ตบผลลัพธ์ใดที่ spread ระหว่าง range กับ median เกิน 15% และทิ้งการจับเวลาทุกครั้งที่ PDF ที่เซฟออกมาไม่ผ่านการตรวจด้านโครงสร้าง การ render หรือความหมาย รายการพวกนี้ฟังดูเป็นระเบียบราชการจนกระทั่งครั้งแรกที่คำโม้ "เร็วขึ้น 20%" ระเหยหายเมื่อรันซ้ำ สิ่งที่ต่อจากนี้คือ probe corpus เฉพาะทางกับ comparison runner ของมันไปถึงตรงนั้นอย่างไร รวมถึงรอบที่เครื่องยุ่งเกินกว่าจะวัดอะไรได้เลยและ harness ก็ปฏิเสธอย่างถูกต้อง
ทำไม benchmark PDF ใน Delphi ถึงรายงานศูนย์วินาที
benchmark โหลด PDF รายงานศูนย์วินาทีเมื่อนาฬิกาของมันเดินคร่าวกว่า operation ที่มันวัด และ GetTickCount64 ก็คือนาฬิกาแบบนั้นเต็ม ๆ: มันคืนค่าหน่วยมิลลิวินาที แต่บน Windows มันเดินเฉพาะตอน system timer interrupt ยิง ซึ่งมักทุก ๆ 15.6 ms พอร์ต FPC ของ demo benchmark ไฟล์ใหญ่ใน PDF Library for Delphi ใช้ตัวนี้เพราะ TStopwatch ไม่มีใน toolchain นั้น และมันบันทึกเวลาที่ใช้ไปถึงทศนิยมสามตำแหน่ง การโหลดภาพ CAD เล็ก ๆ หรือเอกสาร tagged สั้น ๆ จบในกลาง ๆ หนึ่งจังหวะ timer อย่างสบาย บางรอบ demo เลยพิมพ์ 0.000 ออกมาสำหรับการโหลดที่แท้จริงทำงานเต็ม ๆ
function ElapsedSeconds(StartTick: QWord): Double;
begin
Result:= (GetTickCount64- StartTick)/ 1000.0;
end;
// ข้างใน loop ของ operation
Lib:= TPDFlib.Create;
try
Lib.OnProgress:= Reporter.Progress;
Started:= GetTickCount64;
LoadCode:= Lib.LoadFromFile(InputFile, Password);
...
ศูนย์แย่กว่าตัวเลขที่ไม่แม่น เพราะการเปรียบเทียบทุกอย่างที่คุณสร้างบนมันต้องหารด้วยมัน comparison runner แบบคู่ถือว่า arm ใดที่ค่าต่ำสุดเป็นศูนย์ไม่สรุปผลได้ ด้วยเหตุผลว่า "Zero duration prevents a meaningful ratio" ซึ่งเป็นการปฏิเสธที่ถูกแล้ว แต่มันยังแปลว่าการจับเวลาของ demo ทิ้งช่องว่างการวัดไว้พอดีตรงที่ไฟล์สั้นอาศัยอยู่ และ demo ตัวเดิมยังติดตั้ง callback OnProgress อีก การจับเวลาของมันจึงพา overhead ของ callback ติดไปด้วย ซึ่งการวัด load/save ที่สะอาดไม่ควรแบกรับ และตัวเลข demo ที่เก็บไว้ก็สลับใช้กับอะไรที่วัดทีหลังไม่ได้เลย
จับเวลา LoadFromFile กับ SaveToFile ด้วย QueryPerformanceCounter
probe console เฉพาะทาง Tests/CorpusLoadSave.dpr วัดสอง operation ต่อไฟล์ input ด้วย QueryPerformanceCounter: LoadFromFile บวกการอ่าน PageCount และ LoadFromFile บวก PageCount บวก SaveToFile แต่ละ operation ได้ instance TPDFlib ใหม่เอี่ยมและไม่มี progress callback ส่วน constructor กับ destructor ของ instance อยู่นอกเขตที่จับเวลา เช่นเดียวกับการเขียน CSV และการตรวจ output ทั้งหมด counter ถูกอ่านก่อน load ทันทีและหลัง call library ตัวสุดท้ายทันที และ LastErrorCode ถูกดึงหลังการอ่านครั้งที่สองเท่านั้น
Lib:= TPDFlib.Create;
try
if not QueryPerformanceCounter(Started) then
raise Exception.Create('Performance counter unavailable');
Code:= Lib.LoadFromFile(WideString(SourceFile), '');
if Code= 1 then
begin
Pages:= Lib.PageCount;
if Save then Code:= Lib.SaveToFile(WideString(SavedFile));
end;
if not QueryPerformanceCounter(Finished) then
raise Exception.Create('Performance counter unavailable');
ErrorCode:= Lib.LastErrorCode;
finally
Lib.Free;
end;
Ticks:= Finished- Started;
if Ticks< 0 then
raise Exception.Create('Performance counter moved backwards');
probe เขียนจำนวน tick ดิบกับความถี่ของ counter ไว้ข้างวินาทีที่คำนวณได้ จัดรูปแบบเป็นทศนิยมเก้าตำแหน่งและใช้ตัวคั่นทศนิยม . ตายตัว ใครก็คำนวณผลหารกลับจาก CSV ได้แทนที่จะเชื่อมันซะเฉย ๆ บนบิลด์ FPC Win64 ตัวอย่าง CAD โหลดใน 8,888 tick ที่ 10,000,000 tick ต่อวินาที บันทึกไว้เป็น 0.000888800 วินาที — การสังเกตที่ timer ตัวเก่าจะปัดเป็นศูนย์ไปแล้ว probe ตั้งใจไม่ตัดค่าสั้นทิ้ง ไม่แทนที่ด้วยระยะเวลาขั้นต่ำ หรือหัก overhead ของ timer ที่ประมาณไว้ และยังเขียนทั้งสองแถวออกมาพร้อม exit code ไม่เป็นศูนย์เมื่อ call library ล้มเหลว แต่เก้าหลักไม่ใช่ความแม่น: precision ที่บันทึกมากขึ้นไม่ได้บอกอะไรเรื่องความซ้ำซาก และการสังเกตที่ noisy หรือเป็นศูนย์ก็ยังต้องถูกตบทิ้งในขั้นถัดไป
if not QueryPerformanceFrequency(Frequency) or (Frequency<= 0) then
raise Exception.Create('Performance counter frequency unavailable');
NumberFormat:= TFormatSettings.Create;
NumberFormat.DecimalSeparator:= '.';
...
Rows.Add(CSV(ExtractFileName(SourceFile))+ ','+ CSV(Operation)+ ','+
FormatFloat('0.000000000', Ticks/ Frequency, NumberFormat)+ ','+
IntToStr(Code)+ ','+ IntToStr(ErrorCode)+ ','+ IntToStr(Pages)+ ','+
IntToStr(Ticks)+ ','+ IntToStr(Frequency));
อะไรทำให้การเทียบเวลา load/save ของ PDF น่าเชื่อถือ
การเทียบเวลาระหว่างบิลด์สองตัวของ PDF Library for Delphi น่าเชื่อถือได้เมื่อลำดับการเริ่ม เงื่อนไขการเริ่ม และ spread ถูกควบคุมและบันทึกไว้ครบ comparison runner จึงจัดคู่อย่างน้อยสามคู่ตามลำดับ A/B, B/A, A/B การรัน baseline ก่อนเสมอค่อย ๆ ส่ง file cache ที่อุ่นและสถานะความร้อนที่ต่างออกไปให้ candidate อย่างเงียบ ๆ การสลับลำดับเป็นวิธีกระจาย bias นั้นให้ทั้งสอง arm แทนที่จะยกให้ arm เดียว ก่อนแต่ละ arm runner จะ hash ไฟล์ input ทั้งไฟล์ด้วย SHA-256 ซึ่งทั้งยืนยันว่าไม่มีอะไรเปลี่ยนและพรีอ่านไบต์เดียวกันให้ทั้งสอง arm และมัน hash ไฟล์ executable ทั้งสองกับเครื่องมือตรวจอีกครั้งหลังทุกรอบ เพื่อไม่ให้ binary ที่ build ใหม่แทรกเข้ามากลาง series ได้
จากนั้น runner สุ่มตัวการใช้ CPU ทั้งเครื่องวินาทีละครั้งและเริ่ม arm เมื่อตัวอย่างลงมาที่ 25% หรือต่ำกว่าเท่านั้น โดยรอไม่เกิน 30 วินาทีก่อนบันทึกความพยายามนั้นว่าถูกปฏิเสธ ประตูบานนี้ควบคุมเงื่อนไขการเริ่มเท่านั้นและไม่ได้ควบคุมอย่างอื่นเลย: มันไม่ได้ปิดเครื่องจากโลกภายนอกระหว่างรัน สถานะพลังงาน, thermal throttling, งานเบื้องหลัง และ OS caching ยังขยับตัวเลขได้อยู่ ตัวกรองที่สองจึงเป็นเรื่องสถิติในความหมายธรรมดาที่สุด สำหรับแต่ละ operation runner คำนวณ range หารด้วย median ของ arm baseline, arm candidate และการกระจายของ ratio candidate/baseline แบบคู่ และถ้าอะไรในสามอย่างเกิน 0.15 ผลลัพธ์จะถูกติดป้ายว่า noisy แทนที่จะรายงานเป็นข้อค้นพบ
ทำไม control ที่ใช้ binary เดียวกันจึงพิสูจน์ความซ้ำซาก ไม่ใช่ความเร็ว
control ที่ใช้ binary เดียวกันรัน executable ชุดเดียวกันทั้ง baseline และ candidate ดังนั้น ratio ใกล้ 1.0 พิสูจน์ได้แค่ว่า setup การวัดซ้ำให้ผลเดิมเท่านั้น มันจะไม่มีวันแสดงให้เห็นว่า implementation เร็วขึ้น control เข้มงวดแรกในวันที่ 2026-09-21 ใช้ probe FPC Win64 ที่มี resolution สูงกับเอกสาร tagged 70 หน้าที่รับรองไว้ และทั้งหกเริ่มงานถูกปฏิเสธหมดเพราะตัวอย่าง CPU ไล่จาก 26.5% ไปถึง 93.8% รายงานมีแต่ความล้มเหลวไม่มีค่ารวม ซึ่งเป็นผลลัพธ์ที่คุณต้องการพอดีตอนเครื่องยุ่ง รอบ retry ในวันเดียวกันด้วย input ที่ไบต์เหมือนกันทุกไบต์, probe executable ตัวเดิมและเกณฑ์เดิม ยอมรับทั้งหกเริ่มงานภายใน 3 วินาที spread ระหว่าง range กับ median ทุกตัวอยู่ระหว่าง 0.019 กับ 0.054 และ median ของ ratio อยู่ที่ 1.0084 สำหรับ LoadFromFile และ 0.9872 สำหรับ LoadFromFile + SaveToFile
ตัวเลขคู่นี้สถาปนาหน้าต่างการสังเกตแบบมีเงื่อนไขและไม่ไปไกลกว่านั้น เมื่อ binary สองตัวต่างกัน รอบที่นิ่งจะถูกติดป้ายว่าเป็นการเปรียบเทียบเชิงบรรยาย พร้อมหมายเหตุชัดเจนว่า ratio เหล่านี้เป็นการสังเกต ไม่ใช่นัยสถิติหรือคำอ้างว่าเร็วขึ้น วินัยแบบนี้สำคัญที่สุดตอนที่คุณ validate การ optimize แบบเจาะจงเป้าอย่างที่เล่าไว้ในการ profiling PDF Library for Delphi และแทนที่ hot path ด้วย hash index: profiler บอกได้ว่าเวลาไหลไปที่ไหน แต่มีแค่รอบคู่แบบควบคุมบนเอกสารจริงเท่านั้นที่บอกได้ว่าการเปลี่ยนรอดจากการปะทะกับ pipeline ทั้งสายหรือไม่ อีกเขตแดนหนึ่งที่ควรพูดให้ชัด — normal-save รวมการโหลดเข้าไปด้วย และ peak working set ที่ runner บันทึกเป็นของทั้งโปรเซส ไม่มีส่วนไหนเลยที่เป็นหน่วยความจำที่โยนให้การเซฟเพียงลำพังได้
ประตู output สามบานกับ matrix สี่ compiler
การจับเวลาใด ๆ ของ PDF Library for Delphi จะไม่มีผลเว้นแต่ไฟล์ที่มันผลิตจะผ่านประตูอิสระสามบาน เพราะการเซฟที่เขียน PDF พังออกมาเร็ว ๆ ไม่ใช่การเซฟที่เร็วขึ้น benchmark ตรวจก่อนว่าทั้งสอง operation คืน 1 และรายงานจำนวนหน้าตามที่รับรองไว้ แล้วจึง validate PDF ที่เซฟเดียวตามลำดับนี้:
- โครงสร้าง: checker PDF อิสระต้องผ่านไฟล์ที่เซฟโดยไม่มี error หรือ warning
- การ render: ทุกหน้าถูก render ในสถานะ default และชุด SHA-256 ของภาพต่อหน้าต้องตรงกับการ render อ้างอิงของต้นทางที่รับรองไว้ทุกประการ
- ความหมายที่ไม่ใช่ภาพ: การเทียบเชิงความหมายแยกต่างหากกับต้นทางครอบคลุม property ที่เลือกไว้ซึ่งพิกเซลแสดงให้เห็นไม่ได้ รวมถึงโครงสร้าง optional-content และการวัดภายในขอบเขตที่เอกสารระบุไว้
เมื่อประตูพวกนี้พร้อมแล้ว matrix corpus ทั้งหมดในเครื่องรัน probe บน FPC Win32, FPC Win64, Delphi Win32 และ Delphi Win64 เหนือ PDF ที่รับรองไว้ 12 ไฟล์รวม 1,612 หน้าต้นทาง ได้คู่ sample/target 48 คู่และหน้า output ที่ผ่านการตรวจ 6,448 หน้าโดยไม่มีความต่างเชิงความหมายที่เลือกไว้เลย การวัด operation ทั้ง 96 รายการยังถือค่า counter ดิบเป็นบวกที่สอดคล้องกับวินาทีที่รายงาน และค่าพวกนี้ถูกตั้งใจไม่รวมขึ้นเป็นตารางความเร็วข้าม compiler เพราะ matrix นี้เป็นหลักฐานเชิงฟังก์ชัน ไม่ใช่การเปรียบเทียบแบบควบคุม เส้นทาง load/save ก็ไม่ได้อ้างว่า decode ภาพ embedded ทุกภาพ, validate ลายเซ็น, รัน XFA หรือรับรอง PDF/UA ถ้าต้องการตัดสิน throughput ของการ render แทนต้นทุน load/save ข้อจำกัดด้าน concurrency ในการ render หน้าแบบขนานและความปลอดภัยเรื่อง thread ใน PDF Library for Delphi เป็นจุดเริ่มที่ดีกว่า
ข้อสรุปที่ใช้ได้จริงสั้น ๆ: เก็บ counter ดิบไว้ สลับลำดับ ติดประตูตอนเริ่ม ปฏิเสธ spread ที่ noisy และอย่าจับเวลา output ที่คุณยังไม่ได้ตรวจ กฎพวกนี้คือสิ่งที่ทำให้ PDF Library for Delphi พูดว่า "ไม่มีการเปลี่ยนที่วัดได้" ได้มั่นใจพอ ๆ กับพูดว่า "เร็วขึ้น" และ source ของ probe ตัวเดียวกัน compile แบบไม่แก้อะไรได้ทั้งบน Delphi และ FPC ทั้ง Win32 กับ Win64 คุณดู library, API ด้าน load/save และ compiler ที่รองรับได้ที่หน้าผลิตภัณฑ์ PDF Library for Delphi