Un benchmark load/save onest pentru PDF Library for Delphi cronometrează LoadFromFile și SaveToFile cu QueryPerformanceCounter, păstrează tick-urile brute și frecvența contorului, rulează baseline și candidat în perechi alternante A/B, B/A, A/B, refuză să pornească cât timp încărcarea CPU stă peste 25%, respinge orice rezultat a cărui dispersie range/mediană depășește 15% și aruncă orice măsurătoare al cărei PDF salvat pică la validarea structurală, la randare sau la validarea semantică. Lista pare birocrație până la prima dată când o afirmație de genul „20% mai rapid” se evaporă la o rerulare. Ce urmează este povestea felului în care probe-ul dedicat de corpus și runner-ul lui de comparație au ajuns acolo, inclusiv rularea în care mașina era pur și simplu prea ocupată ca să măsoare ceva și harness-ul a spus corect acest lucru
De ce raportează un benchmark PDF în Delphi zero secunde?
Un benchmark de încărcare PDF raportează zero secunde când ceasul lui tace mai grosier decât operațiunea măsurată, iar GetTickCount64 este exact genul acela de ceas: întoarce milisecunde, dar pe Windows avansează doar când se declanșează întreruperea timer-ului de sistem, de regulă la fiecare 15,6 ms. Portul FPC al demo-ului de benchmark pentru fișiere mari din PDF Library for Delphi îl folosea pentru că TStopwatch nu e disponibil în acel toolchain, iar el înregistrează timpul scurs cu trei zecimale. Încărcarea unui desen CAD mic sau a unui document scurt etichetat se termină bine în interiorul unui pas de timer, deci demo-ul afișa uneori 0.000 pentru o încărcare care în mod clar făcea muncă reală
function ElapsedSeconds(StartTick: QWord): Double;
begin
Result:= (GetTickCount64- StartTick)/ 1000.0;
end;
// în interiorul buclei de operații
Lib:= TPDFlib.Create;
try
Lib.OnProgress:= Reporter.Progress;
Started:= GetTickCount64;
LoadCode:= Lib.LoadFromFile(InputFile, Password);
...
Un zero e mai rău decât un număr imprecis, pentru că fiecare comparație construită pe el împarte la el. Runner-ul de comparație pereche tratează orice ramură cu minimul zero ca neconcludentă, cu motivul „o durată de zero împiedică un raport cu sens”, ceea ce e refuzul corect, dar înseamnă și că măsurătorile demo-ului lăsau un gol exact acolo unde trăiesc fișierele scurte. Același demo instalează și un callback OnProgress, deci măsurătorile lui includ overhead-ul callback-ului, pe care o măsurătoare curată load/save nu ar trebui să îl care, iar numerele demo arhivate nu sunt interschimbabile cu nimic măsurat ulterior
Cronometrarea LoadFromFile și SaveToFile cu QueryPerformanceCounter
Probe-ul dedicat de consolă, Tests/CorpusLoadSave.dpr, măsoară două operații per fișier de intrare cu QueryPerformanceCounter: LoadFromFile plus citirea lui PageCount, și LoadFromFile plus PageCount plus SaveToFile. Fiecare operație primește o instanță TPDFlib proaspătă și niciun callback de progres, iar constructorul și destructorul instanței stau în afara regiunii cronometrate, la fel ca scrierea CSV și toată validarea rezultatului. Contorul e citit imediat înainte de încărcare și imediat după ultimul apel al bibliotecii, iar LastErrorCode e citit doar după a doua citire
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-ul scrie numărul brut de tick-uri și frecvența contorului lângă secundele derivate, formatate cu nouă zecimale și separator zecimal fix ., deci oricine poate recalcula câtul din CSV în loc să aibă încredere în el. Pe build-ul FPC Win64, exemplul CAD s-a încărcat în 8.888 de tick-uri la 10.000.000 de tick-uri pe secundă, înregistrat ca 0.000888800 secunde — o observație pe care vechiul timer ar fi rotunjit-o la zero. Probe-ul nu decoltează în mod deliberat valorile scurte, nu substituie o durată minimă și nu scade un overhead estimat al timer-ului, și scrie în continuare ambele rânduri cu cod de ieșire nenul când un apel al bibliotecii eșuează. Nouă cifre nu înseamnă însă acuratețe: mai multă precizie înregistrată nu spune nimic despre repetabilitate, iar observațiile zgomotoase sau zero trebuie totuși respinse în aval
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));
Ce face de încredere o comparație de timpi Load/Save PDF?
O comparație de timpi între două build-uri ale PDF Library for Delphi e de încredere doar când ordinea pornirii, condițiile de pornire și dispersia sunt toate controlate și înregistrate, deci runner-ul de comparație programează cel puțin trei perechi în ordinea A/B, B/A, A/B. Rularea baseline-ului întotdeauna primul îi dă în tăcere candidatului un cache de fișiere mai cald și o stare termică diferită; alternarea ordinii împrăștie acel bias pe ambele ramuri în loc să îl crediteze uneia. Înainte de fiecare ramură, runner-ul hash-uiește fișierul de intrare complet cu SHA-256, ceea ce verifică că nimic nu s-a schimbat și pre-citește aceiași octeți pentru oricare ramură, iar după fiecare rulare hash-uiește din nou cele două executabile și uneltele de validare, ca un binar reconstruit să nu poată aluneca în mijlocul unei serii
Runner-ul eșantionează apoi o dată pe secundă utilizarea CPU la nivel de mașină și pornește ramura doar când un eșantion coboară la 25% sau sub, așteptând cel mult 30 de secunde înainte de a înregistra tentativa ca respinsă. Poarta aceea controlează condiția de pornire și nimic altceva: nu izolează mașina în timpul rulării, iar starea de alimentare, throttling-ul termic, munca în fundal și caching-ul OS pot muta în continuare numerele. De aceea al doilea filtru e statistic, în sensul cel mai simplu. Pentru fiecare operație, runner-ul calculează range-ul împărțit la mediană pentru ramura baseline, ramura candidat și distribuția rapoartelor pereche candidat/baseline, iar dacă oricare dintre cele trei depășește 0,15, rezultatul e etichetat zgomotos în loc să fie raportat ca un finding
De ce un control cu același binar dovedește repetabilitate, nu viteză?
Un control cu același binar rulează executabile identice ca baseline și candidat, deci un cât aproape de 1,0 poate dovedi doar că configurația de măsurare se repetă pe sine; nu poate arăta niciodată că o implementare a devenit mai rapidă. Primul control strict, din 2026-09-21, a folosit probe-ul FPC Win64 high-resolution pe un ghid etichetat recunoscut de 70 de pagini, iar toate cele șase porniri au fost respinse pentru că eșantioanele CPU au variat între 26,5% și 93,8%. Raportul conținea eșecuri și niciun agregat, exact rezultatul dorit când mașina e ocupată. O reîncercare în aceeași zi, cu input-uri octet-cu-octet identice, același executabil de probe și praguri neschimbate, a acceptat toate cele șase porniri în mai puțin de 3 secunde; fiecare dispersie range/mediană a aterizat între 0,019 și 0,054, iar medianele rapoartelor au fost 1,0084 pentru LoadFromFile și 0,9872 pentru LoadFromFile + SaveToFile
Perechea aceea de numere stabilește o fereastră de observație calificată și nimic mai mult. Când cele două binare diferă, o rulare stabilă e etichetată comparație descriptivă, cu nota explicită că rapoartele sunt observații, nu semnificație statistică sau o afirmație de accelerare. Disciplina contează cel mai mult când validați optimizări ținte precum cele descrise în profilarea PDF Library for Delphi și înlocuirea căilor fierbinți cu hash indexes: un profiler vă spune unde se duce timpul, dar doar o rulare pereche controlată pe documente reale vă spune dacă schimbarea a supraviețuit contactului cu tot pipeline-ul. Încă o graniță care merită spusă cu voce tare — normal-save include încărcarea, iar peak-ul working set pe care îl înregistrează runner-ul e la nivel de proces, deci nimic din el nu e memorie atribuibilă doar salvării
Trei porți de output și o matrice de patru compilatoare
Niciun timp din PDF Library for Delphi nu contează dacă fișierul produs nu trece de trei porți independente, pentru că o salvare care scrie repede un PDF stricat nu e o salvare mai rapidă. Benchmark-ul verifică întâi că ambele operații au întors 1 și au raportat numărul recunoscut de pagini, apoi validează PDF-ul salvat unic în ordinea aceasta:
- Structură: un checker PDF independent trebuie să treacă fișierul salvat fără erori sau avertismente
- Randare: fiecare pagină e randată în starea ei implicită, iar mulțimea de SHA-256 per pagină a imaginilor trebuie să se potrivească exact cu randarea de referință a sursei recunoscute
- Semantică non-vizuală: o comparație semantică separată față de sursă acoperă proprietăți selectate pe care pixelii nu le pot arăta, inclusiv structurile optional-content și de măsurare în limita scopului lor documentat
Cu porțile astea la locul lor, matricea completă de corpus locală a rulat probe-ul pe FPC Win32, FPC Win64, Delphi Win32 și Delphi Win64 peste 12 PDF-uri recunoscute cu 1.612 de pagini sursă, dând 48 de perechi sample/target și 6.448 de pagini de rezultat validate fără diferențe semantice selectate. Toate cele 96 de măsurători de operații au păstrat valori brute pozitive de contor, consistente cu secundele raportate, iar valorile acelea nu sunt aggregate în mod deliberat într-un tabel de viteză cross-compilator, pentru că matricea e dovadă funcțională, nu o comparație controlată. Calea load/save nu pretinde nici că decodează fiecare imagine încorporată, că validează semnături, că execută XFA sau că certifică PDF/UA; dacă trebuie să judecați throughput-ul de randare, nu costul load/save, constrângerile de concurență din randarea paralelă de pagini și thread safety în PDF Library for Delphi sunt punctul de plecare mai bun
Concluzia practică e scurtă: păstrați contoarele brute, alternați ordinea, puneți poartă la pornire, refuzați dispersiile zgomotoase și nu cronometrați niciodată un rezultat pe care nu l-ați validat. Regulile astea sunt ce îi permit PDF Library for Delphi să spună „nicio schimbare măsurabilă” la fel de încrezător ca „mai rapid”, iar același cod sursă de probe se compilează neschimbat pe Delphi și FPC pentru Win32 și Win64. Puteți revisa biblioteca, API-ul ei load/save și compilatoarele suportate pe pagina de produs PDF Library for Delphi