Честен load/save benchmark за PDF Library for Delphi мери LoadFromFile и SaveToFile с QueryPerformanceCounter, пази суровите тикове и честотата на брояча, върти baseline и candidate в редуващи се A/B, B/A, A/B двойки, отказва да стартира, докато CPU натоварването стои над 25%, отхвърля всеки резултат, чийто разсейване спрямо медиана минава 15%, и изхвърля всяко измерване, при което записаният PDF не мине структурна, рендираща или семантична валидация. Списъкът звучи като бюрокрация, докато не дойде моментът, в който претенцията за „20% по-бързо" се изпарява при повторение. Следващото е как corpus probe-ът и неговият comparison runner стигнаха дотам, включително и изпълнението, при което машината просто беше твърде заета да мери каквото и да е, а harness-ът го каза коректно
Защо Delphi PDF benchmark съобщава нула секунди?
PDF load benchmark съобщава нула секунди, когато часовникът му тиква по-грубо от операцията, която мери, а GetTickCount64 е точно такъв часовник: връща милисекунди, но под Windows напредва само когато сработи системното таймерно прекъсване, обикновено на всеки 15.6 ms. FPC портът на демото за големи файлове в PDF Library for Delphi го ползваше, защото TStopwatch не е наличен в тази toolchain, и записваше изминалото време до три знака след десетичната запетая. Зареждането на малък CAD чертеж или кратък tagged документ приключва далеч в рамките на една таймерна стъпка, така че демото понякога печатеше 0.000 за load, който очевидно вършеше истинска работа
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);
...
Нулата е по-зле от неточно число, защото всяко сравнение, което изградите върху нея, дели на нея. Сдвоеният comparison runner третира всяка страна с нулев минимум като неубедителна с причина „Zero duration prevents a meaningful ratio" — това е правилният отказ, но означава и, че демонстрационните времена оставяха дупка в измерването точно там, където живеят късите файлове. Същото демо инсталира и OnProgress callback, така че неговите времена включват overhead от callback, който чисто load/save измерване не бива да носи, а архивните демонстрационни числа не са взаимозаменяеми с нищо, измерено по-късно
Измерване на LoadFromFile и SaveToFile с QueryPerformanceCounter
Посветеният console probe, Tests/CorpusLoadSave.dpr, мери две операции на входен файл с QueryPerformanceCounter: LoadFromFile плюс четене на PageCount, и LoadFromFile плюс PageCount плюс SaveToFile. Всяка операция получава свеж TPDFlib екземпляр и никакъв progress callback, а конструкторът и деструкторът на екземпляра остават извън измерваната зона, както и записът на 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');
Probe-ът записва суровия брой тикове и честотата на брояча до изчислените секунди, форматирано с девет знака след десетичната запетая и фиксиран . десетичен разделител, така че всеки може да преизчисли частното от CSV-а, вместо да му вярва. На FPC Win64 build CAD примерът се зареди за 8 888 тика при 10 000 000 тика в секунда, записано като 0.000888800 секунди — наблюдение, което старият таймер би закръглил до нула. Probe-ът нарочно не отрязва къси стойности, не замества с минимална продължителност и не изважда оценен таймерен overhead, и пак записва двата реда с ненулев exit код, когато извикване на библиотеката се провали. Деветте цифри не са точност, обаче: повече записана прецизност не казва нищо за повторяемост, а шумните или нулеви наблюдения и нататък трябва да се отхвърлят надолу по веригата
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 load/save времена достоверно?
Сравнение на времена между два build-а на PDF Library for Delphi е достоверно само когато стартовият ред, стартовите условия и разсейването са контролирани и записани, затова comparison runner-ът планира поне три двойки в ред A/B, B/A, A/B. Винаги първият пуск на baseline тихо подарява на candidate по-топъл file cache и друго термично състояние; редуването на реда разпределя това пристрастие и на двете страни, вместо да го запише на едната. Преди всяка страна runner-ът хешира пълния входен файл със SHA-256, което едновременно проверява, че нищо не се е променило, и предварително прочита същите байтове за всяка страна, а двата изпълними файла и валидиращите инструменти се хешират пак след всяко изпълнение, така че прекомпилиран binary не може да се промъкне по средата на серията
После runner-ът пробва цялосистемното CPU натоварване по веднъж в секунда и пуска страната само когато някое пробване падне на 25% или под, чакайки най-много 30 секунди, преди да запише опита като отхвърлен. Тази врата контролира само стартовото условие и нищо друго: тя не изолира машината по време на изпълнението, а power state, thermal throttling, фоновата работа и OS кеширането и нататък могат да местят числата. Затова вторият филтър е статистически в най-простия смисъл. За всяка операция runner-ът смята размах, делен на медиана, за baseline страната, за candidate страната и за разпределението на сдвоените candidate/baseline съотношения, и ако някое от трите мине 0.15, резултатът се етикетира като шумен, вместо да се докладва като находка
Защо same-binary контролата доказва повторяемост, а не скорост?
Same-binary контрола пуска идентични изпълними файлове като baseline и като candidate, така че съотношение около 1.0 може само да докаже, че измервателната постановка се повтаря; никога не може да покаже, че една имплементация е станала по-бърза. Първата строга контрола на 2026-09-21 ползваше high-resolution FPC Win64 probe срещу признато ръководство с 70 страници, а всичките шест старта бяха отхвърлени, защото CPU пробванията се движеха между 26.5% и 93.8%. Докладът съдържаше провали и никакви агрегати — точно изходът, който искате, когато машината е заета. Повторение същия ден с байт-идентични входове, същия probe изпълним файл и непроменени прагове прие всичките шест старта в рамките на 3 секунди; всяко разсейване спрямо медиана легна между 0.019 и 0.054, а медианите на съотношенията бяха 1.0084 за LoadFromFile и 0.9872 за LoadFromFile + SaveToFile
Тази двойка числа утвърждава квалифициран прозорец за наблюдение и нищо повече. Когато двата изпълними файла се различават, стабилен пуск се етикетира като описателно сравнение, с изричната бележка, че съотношенията са наблюдения, а не статистическа значимост или претенция за ускорение. Дисциплината има най-голямо значение, когато валидирате насочени оптимизации като описаните в профилирането на PDF Library for Delphi и заменянето на горещи пътища с hash индекси: profiler-ът ви казва накъде отива времето, но само контролиран сдвоен пуск върху истински документи ви казва дали промяната е оцеляла при контакта с цялата линия. Още една граница, която си струва да се изрече на глас — normal-save включва зареждане, а пикованият working set, който runner-ът записва, е за целия процес, така че нищо от това не е памет, дължима само на записа
Три изходни врати и матрица от четири компилатора
Никое време на PDF Library for Delphi не се брои, освен ако файлът, който е произвело, не мине три независими врати, защото запис, който бързо пише развален PDF, не е по-бърз запис. Benchmark-ът първо проверява, че двете операции са върнали 1 и съобщили признатия брой страници, после валидира единствения записан PDF в този ред:
- Структура: независим PDF checker трябва да пропусне записания файл без грешки или предупреждения
- Рендиране: всяка страница се рендира в състоянието си по подразбиране, а множеството от SHA-256 на образите по страници трябва да съвпада точно с референтното рендиране на признатия източник
- Невизуална семантика: отделно семантично сравнение с източника покрива избрани свойства, които пикселите не могат да покажат, включително optional-content и measurement структури в рамките на документирания им обхват
С тези врати на място пълната локална corpus матрица пусна probe-а на FPC Win32, FPC Win64, Delphi Win32 и Delphi Win64 върху 12 признати PDF-а с 1 612 изходни страници, което даде 48 sample/target двойки и 6 448 валидирани изходни страници без избрани семантични разлики. Всичките 96 измервания на операции запазиха положителни сурови стойности на брояча, съгласувани с докладваните им секунди, а тези стойности нарочно не се агрегират в таблица за скорост между компилатори, защото матрицата е доказателство за функционалност, а не контролирано сравнение. Load/save пътят също не претендира да декодира всяко вградено изображение, да валидира подписи, да изпълнява XFA или да сертифицира PDF/UA; ако трябва да оцените пропускателната способност на рендиране, а не цената на load/save, ограниченията за паралелизъм в паралелното рендиране на страници и thread safety в PDF Library for Delphi са по-добрата отправна точка
Практическият извод е кратък: пазете суровите броячи, редувайте реда, оградете старта, отказвайте шумно разсейване и никога не мерете изход, който не сте валидирали. Тези правила са това, което позволява на PDF Library for Delphi да каже „няма измерима промяна" със същата увереност като „по-бързо", а същият probe код се компилира непроменен на Delphi и FPC за Win32 и Win64. Можете да прегледате библиотеката, нейния load/save API и поддържаните компилатори на продуктовата страница на PDF Library for Delphi