Tehnični članak

Iskren benchmark nalaganja in shranjevanja PDF v Delphiju

Iskren primerjalni preizkus nalaganja in shranjevanja za PDF Library for Delphi meri LoadFromFile in SaveToFile s QueryPerformanceCounter, hrani surove tike in frekvenco števca, poganja izhodišče in kandidata v izmeničnih parih A/B, B/A, A/B, odkloni zagon, dokler je obremenitev CPU nad 25 %, zavrne vsak rezultat, katerih razpon glede na mediano preseže 15 %, in vrže proč vsak odmik, pri katerem shranjeni PDF ne prestane strukturnega, upodobitvenega ali semantičnega preverjanja. Ta seznam zveni kot birokracija, dokler prvič ne izhlapi trditev »20 % hitreje« ob ponovnem zagonu. Sledi opis, kako sta tam pristala namenski preiskus korpusa in njegov primerjalni poganjalec, vključno s tekom, ko je bil stroj preprosto preveč zaposlen, da bi kaj izmeril, in je okvir to pravilno povedal

Zakaj primerjalni preizkus PDF v Delphiju sporoči nič sekund?

Primerjalni preizkus nalaganja PDF sporoči nič sekund, ko njegova ura tika groboje od operacije, ki jo meri, GetTickCount64 pa je točno taka ura: vrne milisekunde, na Windowsu pa napreduje le, ko se sproži prekinitev sistemskega časovnika, navadno vsakih 15,6 ms. FPC priredba demo primerjave velikih datotek v PDF Library for Delphi jo je uporabila, ker TStopwatch v tej orodni verigi ni na voljo, in zabeleži preteči čas na tri decimalna mesta. Nalaganje majhne CAD risbe ali kratkega označkega dokumenta se konča znotraj enega koraka časovnika, zato je demo včasih izpisal 0.000 za nalaganje, ki je očitno opravilo pravo delo

function ElapsedSeconds(StartTick: QWord): Double;
begin
  Result:= (GetTickCount64- StartTick)/ 1000.0;
end;

// znotraj zanke operacije
Lib:= TPDFlib.Create;
try
  Lib.OnProgress:= Reporter.Progress;
  Started:= GetTickCount64;
  LoadCode:= Lib.LoadFromFile(InputFile, Password);
  ...

Ničla je hujša od nenatančne številke, ker se vsaka primerjava, ki jo zgradite nanjo, deli z njo. Parjeni primerjalni poganjalec obravnava vsako krako z ničelno najmanjšo vrednostjo kot neodločno z razlogom »ničelno trajanje preprečuje smiselno razmerje«, kar je pravilen odklon, hkrati pa pomeni, da so demo odmiki pustili merilno vrzel točno tam, kjer živijo kratke datoteke. Isti demo vgradi še povratni klic OnProgress, zato njegovi odmiki nosijo strošek povratnega klica, ki ga čisto merjenje nalaganja in shranjevanja ne bi smelo nositi, arhivirane demo številke pa niso zamenljive s čimer koli, kar izmerite pozneje

Merjenje LoadFromFile in SaveToFile s QueryPerformanceCounter

Namenski konzolni preiskus Tests/CorpusLoadSave.dpr meri dve operaciji na vhodno datoteko s QueryPerformanceCounter: LoadFromFile plus branje PageCount ter LoadFromFile plus PageCount plus SaveToFile. Vsaka operacija dobi svež primerek TPDFlib in brez povratnega klica napredka, konstruktor in destruktor primerka pa stojita izven merjene regije, tako kot pisanje CSV in vsa preverjanja izhoda. Števec se prebere takoj pred nalaganjem in takoj po zadnjem klicu knjižnice, LastErrorCode pa se poizveda šele po drugem branju

Merjena regija preiskusa korpusa PDFlibPas: QueryPerformanceCounter se prebere takoj pred LoadFromFile in znova po zadnjem klicu knjižnice, znotraj pa sta PageCount in SaveToFile, nastavitev primerka, pisanje CSV, preverjanje izhoda in poizvedba kode napake pa vsi ostanejo izven merjene regije
Surove tike in frekvenca števca se zabeležijo ob izpeljanih sekundah, zato CAD risba, ki se naloži v 8.888 tikah pri desetih milijonih tikah na sekundo, ostane ohranjena kot pravi podatek, namesto da bi jo zaokrožili na nič
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');

Preiskus zapiše surovo število tik in frekvenco števca ob izpeljanih sekundah, oblikovano z devetimi decimalnimi mesti in fiksnim ločilom ., tako da lahko vsak količnik iz CSV izračuna na novo, namesto da bi mu zaupal. Na gradnji FPC Win64 se je vzorec CAD naložil v 8.888 tikah pri 10.000.000 tikah na sekundo, zabeleženo kot 0.000888800 sekund — opazovanje, ki bi ga stari časovnik zaokrožil na nič. Preiskus kratkih vrednosti namenoma ne obreže, ne nadomesti z najmanjšim trajanjem in ne odšteje ocenjenega stroška časovnika, ob odpovedi klica knjižnice pa zapiše obe vrstici z izstopno kodo, različno od nič. Devet števk pa ni natančnost: več zabeležene natančnosti ne pove nič o ponovljivosti, hrupna ali ničelna opazovanja pa je še naprej treba zavreči v nadaljnji obdelavi

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));

Kaj primerjavo odmikov nalaganja in shranjevanja PDF naredi zaupanja vredno?

Primerjava odmikov med dvema gradnjama PDF Library for Delphi je zaupanja vredna le, kadar so vrstni red zagona, pogoji zagona in razpon vsi nadzorovani in zabeleženi, zato primerjalni poganjalec razporedi vsaj tri pare v vrstnem redu A/B, B/A, A/B. Če se vselej najprej požene izhodišče, kandidatu tiho pripade toplejši predpomnilnik datotek in drugačno toplotno stanje; izmenični vrstni red ta pristranost razporedi po obeh krakih, namesto da bi jo pripisal enemu. Pred vsako krako poganjalec izračuna SHA-256 hash celotne vhodne datoteke, kar hkrati preveri, da se nič ni spremenilo, in obeh krakom prebere iste bajte, po vsakem teku pa znova izračuna hash obeh izvršljivih datotek in orodij za preverjanje, tako da obnovljena binarna datoteka ne more privreti na sredo serije

Poganjalec nato enkrat na sekundo vzorči obremenitev CPU na ravni celotnega stroja in začne krako šele, ko vzorec pade na 25 % ali pod, z največ 30 sekundami čakanja, preden poskus zabeleži kot zavrnjen. Ta zapora nadzoruje pogoj zagona in nič drugega: stroja med tekom ne izolira, poraba energije, toplotno dušenje, delo v ozadju in predpomnjenje OS lahko številke še vedno premaknejo. Drugi filter je torej statističen v najbolj golo smislu. Za vsako operacijo poganjalec izračuna razpon deljen z mediano za izhodiščno krako, kandidatsko krako in porazdelitev parjenih razmerij kandidat/izhodišče, če katero koli od treh preseže 0.15, pa je rezultat označen kot hrupen, namesto da bi ga sporočili kot ugotovitev

Zapore parjene primerjave v PDFlibPas: trije pari tečejo v vrstnem redu A/B, B/A, A/B z izračunom SHA-256 vhoda pred vsako krako, zapora zagona čaka na CPU pri 25 % ali pod tem, razponi glede na mediano nad 0.15 pri LoadFromFile ali kraki nalaganje plus SaveToFile pa teko označijo kot hrupen
Izmenični vrstni red zagona razporedi pristranost predpomnilnika in temperature po obeh krakih, nadzor z isto binarno datoteko pa pokaže, kaj takšna postavitev lahko dokaže: razmerja blizu 1.0 utemeljijo ponovljivost, nikoli pa trditev o pospešitvi

Zakaj nadzor z isto binarno datoteko dokaže ponovljivost in ne hitrost?

Nadzor z isto binarno datoteko poganja enaka izvršljiva datoteka kot izhodišče in kandidata, zato lahko razmerje blizu 1.0 dokaže le, da se merilna postavitev ponavlja; nikoli ne more pokazati, da je implementacija postala hitrejša. Prvi strog nadzor 21. 9. 2026 je uporabil preiskus FPC Win64 z visoko ločljivostjo na sprejetem označenem priročniku s 70 stranmi, vseh šest zagonov pa je bilo zavrnjenih, ker so vzorci CPU segali od 26,5 % do 93,8 %. Poročilo je vsebovalo odpovedi in nobenih združitev, kar je točno izid, ki ga želite, ko je stroj zaposlen. Ponovitev istega dne z bajtno identičnimi vhodi, isto izvršljivo datoteko preiskusa in nespremenjenimi pragi je sprejela vseh šest zagonov v 3 sekundah; vsak razpon glede na mediano je pristal med 0.019 in 0.054, mediana razmerij pa je bila 1.0084 za LoadFromFile in 0.9872 za LoadFromFile + SaveToFile

Ta par številk utemelji kvalificirano opazovalno okno in nič več. Ko se binarni datoteki razlikujeta, je stabilen tek označen kot opisna primerjava, z izrecno opombo, da so razmerja opazovanja in ne statistična značilnost ali trditev o pospešitvi. Disciplina je najbolj pomembna, ko potrjujete ciljane optimizacije, kot so tiste, opisane v profiliranju PDF Library for Delphi in zamenjavi vročih poti z hash kazali: profiler pove, kam gre čas, le nadzorovan parjeni tek na pravih dokumentih pa pove, ali je sprememba preživela stik s celotnim cevovodom. Še ena meja, vredna izrekanja na glas — normal-save vključuje nalaganje, najvišji delovni nabor, ki ga poganjalec zabeleži, pa je procesno širok, zato nič od tega ni spomin, pripisljiv samo shranjevanju

Tri zapore izhoda in matrika štirih prevajalnikov

Noben odmik PDF Library for Delphi ne šteje, razen če datoteka, ki jo je ustvaril, prestane tri neodvisne zapore, ker shranjevanje, ki hitro zapiše pokvarjen PDF, ni hitrejše shranjevanje. Benchmark najprej preveri, da sta obe operaciji vrstili 1 in sporočili sprejeto število strani, nato pa preveri en sam shranjeni PDF v tem vrstnem redu:

Tri zapore izhoda v PDFlibPas: obe operaciji morata vrniti 1 s sprejetim PageCount, neodvisni preverjevalnik mora shranjeno datoteko prestati brez opozoril, vsaka stran se mora upodobiti v niz SHA-256 slik na stran, ki se ujema z virom, nevizualna semantika pa se mora ujemati na izbirni vsebini in merilnih strukturah
Shranjevanje, ki hitro zapiše pokvarjen PDF, ni hitrejše shranjevanje, odmik pa šteje šele, ko se struktura, upodobitev in nevizualna semantika vsi strinjajo, da je izhod še vedno isti dokument
  • Struktura: neodvisni preverjevalnik PDF mora shranjeno datoteko prestati brez napak in opozoril
  • Upodobitev: vsaka stran se upodobi v privzetem stanju, niz SHA-256 slik na stran pa se mora točno ujemati z referenčno upodobitvijo sprejetega vira
  • Nevizualna semantika: ločena semantična primerjava z virom pokriva izbrane lastnosti, ki jih piksli ne morejo pokazati, vključno s strukturami izbirne vsebine in merilnimi strukturami znotraj njihovega dokumentiranega obsega

S temi zaporama na mestu je polna lokalna matrika korpusa pognala preiskus na FPC Win32, FPC Win64, Delphi Win32 in Delphi Win64 čez 12 sprejetih PDF-jev z 1.612 izvornimi stranmi, kar da 48 parov vzorec/cilj in 6.448 preverjenih izhodnih strani brez izbranih semantičnih razlik. Vseh 96 merjenj operacij je obdržalo pozitivne surove vrednosti števca, skladne s sporočenimi sekundami, teh vrednosti pa se namenoma ne združi v tabelo hitrosti med prevajalniki, ker je matrika funkcionalen dokaz in ne nadzorovana primerjava. Pot nalaganja/shranjevanja tudi ne trdi, da dekodira vsako vdelano sliko, potrjuje podpise, izvaja XFA ali certificira PDF/UA; če morate oceniti pretočnost upodabljanja in ne strošek nalaganja/shranjevanja, so omejitve sočasnosti v vzporednem upodabljanju strani in varnosti niti v PDF Library for Delphi boljše izhodišče

Praktični izvod je kratek: hranite surove števce, izmenično zaporedje, zaprite zagon, odklonite hrupne razpone in nikoli ne merite izhoda, ki ga niste preverili. Ta pravila so tista, ki PDF Library for Delphi omogočajo povedati »ni merljive spremembe« enako samozavestno kot »hitreje«, isti vir preiskusa pa se na Delphi in FPC za Win32 in Win64 prevede nespremenjen. Knjižnico, njen API nalaganja/shranjevanja in podprte prevajalnike si lahko ogledate na strani izdelka PDF Library for Delphi