一套誠實的 PDF Library for Delphi 載入與儲存基準測試,用 QueryPerformanceCounter 給 LoadFromFile 與 SaveToFile 計時、保留原始 tick 數與計數器頻率、讓基準版與候選版以 A/B、B/A、A/B 成對輪替開跑、CPU 負載停在 25% 以上時拒絕開跑、離散度(全距除以中位數)超過 15% 的結果一律退回,而且只要存出的 PDF 沒過結構、渲染或語意驗證,每一次計時都直接扔掉。這份清單讀起來像官僚流程,直到您第一次看到「快 20%」的宣稱在重跑時蒸發。以下就是專用語料探針與它的比較執行器怎麼走到這一步的,包括那一次機器忙到什麼都量不了、而測試框架正確地把話說出口的經歷
為什麼 Delphi 的 PDF 基準測試會量出零秒?
當時脈的刻度比它要量的操作還粗,載入基準測試就會回報零秒,而 GetTickCount64 正是那種時脈:它回傳毫秒,但在 Windows 上只有系統計時器中斷觸發時才前進,通常是每 15.6 ms 一次。PDF Library for Delphi 的大型檔案基準示範在 FPC 版裡用了它,因為那套工具鏈沒有 TStopwatch,而示範把經過時間記到小數三位。載入一張小型 CAD 圖或一份短的 tagged 文件,遠在一個計時步距之內就做完了,所以示範有時會為明明做了實際工作的載入印出 0.000
function ElapsedSeconds(StartTick: QWord): Double;
begin
Result:= (GetTickCount64- StartTick)/ 1000.0;
end;
// 操作迴圈內部
Lib:= TPDFlib.Create;
try
Lib.OnProgress:= Reporter.Progress;
Started:= GetTickCount64;
LoadCode:= Lib.LoadFromFile(InputFile, Password);
...
零比一個不精確的數字更糟,因為您建立在它之上的每一個比較都要拿它當分母。成對比較執行器把最小值為零的那一臂判定為不可定論,理由是「零時長無法算出有意義的比值」,這個拒絕是對的,但它也代表示範的計時剛好在短檔案所在的區間留下了量測空洞。同一個示範還掛了 OnProgress 回呼,所以它的計時包含了乾淨的載入與儲存量測不該背的回呼開銷,而存檔下來的示範數字,跟之後任何量測都不可互換
用 QueryPerformanceCounter 計時 LoadFromFile 與 SaveToFile
專用的主控台探針 Tests/CorpusLoadSave.dpr 用 QueryPerformanceCounter 對每個輸入檔量兩種操作:LoadFromFile 加讀 PageCount,以及 LoadFromFile 加 PageCount 加 SaveToFile。每種操作都拿到全新的 TPDFlib 實例、不掛進度回呼,而實例的建構與解構都放在計時區間之外,CSV 寫入與所有輸出驗證也是。計數器在載入前一刻、最後一個函式庫呼叫後一刻各讀一次,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');
探針把原始 tick 數與計數器頻率寫在換算秒值旁邊,以九位小數、固定 . 小數點符號格式化,任何人都能從 CSV 重算那個商,而不必單方面相信它。在 FPC Win64 版上,CAD 範例以每秒 10,000,000 tick 的頻率花了 8,888 個 tick 載入,記錄為 0.000888800 秒,舊計時器會把這個觀察值捨入成零。探針刻意不裁切過短的值、不代換最小時長、也不扣掉估計的計時器開銷,而且函式庫呼叫失敗時仍會把兩列都寫出、帶非零結束碼。不過九位數不等於準確度:記錄更多位數的精密度對可重複性隻字未提,帶雜訊或為零的觀察值照樣得在下游拒收
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));
什麼樣的 PDF 載入與儲存計時比較才可信?
兩個 PDF Library for Delphi 版本之間的計時比較,只有在開跑順序、開跑條件與離散度都被控制並記錄下來時才可信,所以比較執行器至少排三對,順序是 A/B、B/A、A/B。永遠讓基準版先跑,等於悄悄把較暖的檔案快取與不同的熱狀態送給候選版;輪替順序是把這個偏差攤到兩臂頭上,而不是記在其中一臂的功勞簿上。每臂開跑前,執行器會用 SHA-256 對完整輸入檔做雜湊,既確認什麼都沒變,也替兩臂預讀同一批位元組;兩個執行檔與驗證工具則在每輪結束後再雜湊一次,重建過的二進位檔別想混進系列中間
接著執行器每秒取樣一次整機 CPU 使用率,等某個樣本降到 25% 以下才開跑該臂,最多等 30 秒,超時就把這次嘗試記為已拒絕。這道閘門只控制開跑條件,僅此而已:它不在執行期間隔離機器,電源狀態、熱節流、背景工作與 OS 快取照樣能搬動數字。所以第二道過濾是最樸素意義上的統計。對每種操作,執行器算出基準臂、候選臂與成對候選除以基準比值分布的全距除以中位數,三者任何一個超過 0.15,結果就標成 noisy,而不是當成結論回報
為什麼同二進位檔對照組證明的是可重複性,而不是速度?
同二進位檔對照組拿完全相同的執行檔當基準版與候選版,所以接近 1.0 的比值只能證明量測架構會自我重複,永遠無法證明實作變快了。2026-09-21 的第一次嚴格對照,用高解析度 FPC Win64 探針對一份收錄的 70 頁 tagged 指南開跑,六次起跑全數被拒,因為 CPU 樣本落在 26.5% 到 93.8% 之間。報告裡只有失敗、沒有彙總,機器忙的時候您要的正是這個結果。同一天稍後重試,輸入逐位元組相同、探針執行檔相同、門檻原封不動,六次起跑在 3 秒內全部放行;每次全距對中位數的離散度都落在 0.019 到 0.054 之間,比值中位數是 LoadFromFile 的 1.0084 與 LoadFromFile + SaveToFile 的 0.9872
這一對數字建立的是一個有條件的觀察窗,僅此而已。當兩個二進位檔不同時,穩定的一輪會標成描述性比較,並附上明白的註記:那些比值是觀察值,不是統計顯著性,更不是加速宣稱。當您在驗證對 PDF Library for Delphi 做效能分析、用雜湊索引替換熱路徑這類針對性的最佳化時,這套紀律最要緊:profiler 告訴您時間花在哪,但只有在真實文件上受控制的成對跑,能告訴您這個改動有沒有撐過整條管線的考驗。還有一個邊界值得說白,normal-save 包含載入,執行器記錄的尖峰工作集是整個 process 的,所以沒有任何一部分是單獨歸給儲存的記憶體
三道輸出閘門與四編譯器矩陣
存出的檔案沒過三道獨立閘門,任何 PDF Library for Delphi 的計時都不算數,因為很快寫出一份壞 PDF 的儲存,不是更快的儲存。基準測試先確認兩種操作都回傳 1、並回報收錄的頁數,然後按以下順序驗證存出的單一 PDF:
- 結構:獨立的 PDF 檢查器必須無錯誤、無警告地通過存出的檔案
- 渲染:每一頁都在預設狀態下渲染,逐頁影像的 SHA-256 集合必須與收錄來源的參考渲染完全一致
- 非視覺語意:另以獨立的語意比對對照來源,涵蓋像素顯現不出來的選定屬性,包括文件記載範圍內的 optional-content 與量測結構
在這些閘門就位之後,完整的本機語料矩陣在 FPC Win32、FPC Win64、Delphi Win32 與 Delphi Win64 上,對 12 份收錄的 PDF、共 1,612 個來源頁面跑探針,得出 48 個樣本對目標組合、6,448 個經驗證的輸出頁面、零個選定語意差異。全部 96 個操作量測都保有與其回報秒值一致的正原始計數器值,而這些值刻意不被彙總成跨編譯器速度表,因為矩陣是功能證據,不是受控比較。載入與儲存路徑也不宣稱能解碼每個內嵌影像、驗證簽章、執行 XFA 或認證 PDF/UA;如果您要評判的是渲染吞吐量而不是載入與儲存成本,PDF Library for Delphi 的平行頁面渲染與執行緒安全裡的並行限制才是更合適的起點
實務上的結論很短:保留原始計數器、輪替順序、給起跑上閘門、拒收帶雜訊的離散度,而且永遠不要替沒驗證過的輸出計時。這些規則讓 PDF Library for Delphi 說「沒有可量測的變化」時,跟說「更快」一樣有底氣,而同一份探針原始碼在 Delphi 與 FPC 的 Win32 與 Win64 上原樣編譯。函式庫、它的載入與儲存 API 以及支援的編譯器,都可以在 PDF Library for Delphi 產品頁上檢視