Ein ehrlicher Load/Save-Benchmark für PDF Library for Delphi misst LoadFromFile und SaveToFile mit QueryPerformanceCounter, behält die rohen Ticks und die Counter-Frequenz, fährt Baseline und Kandidat in alternierenden A/B-, B/A-, A/B-Paaren, startet nicht, solange die CPU-Last über 25 % liegt, verwirft jedes Ergebnis, dessen Range-zu-Median-Streuung über 15 % liegt, und wirft jede Zeitmessung weg, deren gespeichertes PDF die strukturelle, Rendering- oder semantische Validierung nicht besteht. Diese Liste liest sich wie Bürokratie, bis zum ersten Mal, wenn eine Behauptung „20 % schneller“ beim Wiederholungslauf verdampft. Was folgt, ist der Weg, auf dem der dedizierte Korpus-Probe und sein Vergleichsläufer dorthin gekommen sind, inklusive des Laufs, in dem die Maschine schlicht zu beschäftigt war, um irgendetwas zu messen, und das Harness das korrekt gesagt hat
Warum meldet ein Delphi-PDF-Benchmark null Sekunden?
Ein PDF-Load-Benchmark meldet null Sekunden, wenn seine Uhr gröber tickt als die Operation, die sie misst, und GetTickCount64 ist genau so eine Uhr: Sie liefert Millisekunden zurück, aber auf Windows läuft sie nur weiter, wenn der System-Timer-Interrupt feuert, üblicherweise alle 15,6 ms. Der FPC-Port der Large-File-Benchmark-Demo in PDF Library for Delphi nutzte sie, weil es TStopwatch in dieser Toolchain nicht gibt, und zeichnet die verstrichene Zeit mit drei Nachkommastellen auf. Das Laden einer kleinen CAD-Zeichnung oder eines kurzen tagged Dokuments läuft weit innerhalb eines Timer-Schritts ab, also druckte die Demo manchmal 0.000 für ein Laden, das nachweislich echte Arbeit gemacht hat
function ElapsedSeconds(StartTick: QWord): Double;
begin
Result:= (GetTickCount64- StartTick)/ 1000.0;
end;
// innerhalb der Operationsschleife
Lib:= TPDFlib.Create;
try
Lib.OnProgress:= Reporter.Progress;
Started:= GetTickCount64;
LoadCode:= Lib.LoadFromFile(InputFile, Password);
...
Eine Null ist schlechter als eine unpräzise Zahl, denn jeder Vergleich, den Sie darauf bauen, dividiert durch sie. Der Paarvergleichsläufer behandelt jeden Arm mit einem Null-Minimum als inkonklusiv, mit der Begründung „Zero duration prevents a meaningful ratio“ – die korrekte Verweigerung, aber sie bedeutet auch, dass die Demo-Zeiten genau dort eine Messlücke ließen, wo kurze Dateien leben. Dieselbe Demo installiert außerdem einen OnProgress-Callback, ihre Zeiten enthalten also Callback-Overhead, den eine saubere Load/Save-Messung nicht tragen sollte, und archivierte Demo-Zahlen sind mit nichts austauschbar, was später gemessen wurde
LoadFromFile und SaveToFile mit QueryPerformanceCounter messen
Der dedizierte Konsolen-Probe, Tests/CorpusLoadSave.dpr, misst zwei Operationen pro Eingabedatei mit QueryPerformanceCounter: LoadFromFile plus Auslesen von PageCount, und LoadFromFile plus PageCount plus SaveToFile. Jede Operation bekommt eine frische TPDFlib-Instanz und keinen Progress-Callback, und Instanz-Konstruktor und -Destruktor liegen außerhalb der gemessenen Region, ebenso das CSV-Schreiben und die gesamte Ausgabevalidierung. Der Counter wird unmittelbar vor dem Laden und unmittelbar nach dem letzten Bibliotheksaufruf gelesen, und LastErrorCode wird erst nach der zweiten Messung abgeholt
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');
Der Probe schreibt die rohe Tick-Anzahl und die Counter-Frequenz neben den abgeleiteten Sekunden weg, formatiert mit neun Nachkommastellen und festem . als Dezimaltrennzeichen, sodass jeder den Quotienten aus dem CSV nachrechnen kann, statt ihm zu vertrauen. Auf dem FPC-Win64-Build lud die CAD-Stichprobe in 8.888 Ticks bei 10.000.000 Ticks pro Sekunde, notiert als 0.000888800 Sekunden – eine Beobachtung, die der alte Timer auf null gerundet hätte. Der Probe schneidet kurze Werte bewusst nicht ab, ersetzt sie nicht durch eine Mindestdauer und zieht keinen geschätzten Timer-Overhead ab, und er schreibt beide Zeilen trotzdem weg, mit Exit-Code ungleich null, wenn ein Bibliotheksaufruf fehlschlägt. Neun Stellen sind keine Genauigkeit: Mehr notierte Präzision sagt nichts über Wiederholbarkeit, und verrauschte oder nuller Beobachtungen müssen weiterhin downstream verworfen werden
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));
Was macht einen PDF-Load/Save-Zeitvergleich vertrauenswürdig?
Ein Zeitvergleich zwischen zwei Builds der PDF Library for Delphi ist nur dann vertrauenswürdig, wenn Startreihenfolge, Startbedingungen und Streuung kontrolliert und notiert sind, also plant der Vergleichsläufer mindestens drei Paare in A/B-, B/A-, A/B-Reihenfolge. Immer zuerst die Baseline laufen zu lassen schiebt dem Kandidaten still einen wärmeren File-Cache und einen anderen thermischen Zustand zu; die Reihenfolge zu alternieren verteilt diesen Bias über beide Arme, statt ihn einem gutzuschreiben. Vor jedem Arm hasht der Läufer die komplette Eingabedatei mit SHA-256, was zum einen verifiziert, dass sich nichts geändert hat, und zum anderen dieselben Bytes für beide Arme vorliest, und er hasht die beiden Executables und die Validierungswerkzeuge nach jedem Lauf erneut, damit eine neu gebaute Binary nicht mitten in einer Serie einschleichen kann
Danach sampelt der Läufer die CPU-Auslastung maschinenweit einmal pro Sekunde und startet den Arm erst, wenn ein Sample auf 25 % oder darunter fällt, höchstens 30 Sekunden wartend, bevor er den Versuch als abgelehnt notiert. Diese Sperre kontrolliert die Startbedingung und sonst nichts: Sie isoliert die Maschine nicht während des Laufs, und Energiezustand, Thermal-Throttling, Hintergrundarbeit und OS-Caching können die Zahlen weiterhin bewegen. Der zweite Filter ist daher statistisch, im schlichtesten Sinne. Pro Operation berechnet der Läufer Range geteilt durch Median für den Baseline-Arm, den Kandidaten-Arm und die Verteilung der gepaarten Kandidat/Baseline-Verhältnisse, und überschreitet eine der drei Größen 0,15, wird das Ergebnis als verrauscht etikettiert, statt als Befund gemeldet
Warum beweist eine Same-Binary-Kontrolle Wiederholbarkeit, nicht Geschwindigkeit?
Eine Same-Binary-Kontrolle läuft mit identischen Executables als Baseline und Kandidat, also kann ein Verhältnis nahe 1,0 nur belegen, dass der Messaufbau sich selbst wiederholt; es kann niemals zeigen, dass eine Implementierung schneller geworden ist. Die erste strikte Kontrolle am 21.09.2026 nutzte den hochauflösenden FPC-Win64-Probe gegen eine zugelassene 70-seitige tagged Anleitung, und alle sechs Starts wurden abgelehnt, weil die CPU-Samples zwischen 26,5 % und 93,8 % lagen. Der Bericht enthielt Fehler und keine Aggregate – genau das Ergebnis, das Sie wollen, wenn die Maschine beschäftigt ist. Ein Retry am selben Tag mit byte-identischen Eingaben, demselben Probe-Executable und unveränderten Schwellen nahm alle sechs Starts innerhalb von 3 Sekunden an; jede Range-zu-Median-Streuung landete zwischen 0,019 und 0,054, und die Verhältnis-Mediane waren 1,0084 für LoadFromFile und 0,9872 für LoadFromFile + SaveToFile
Dieses Paar von Zahlen etabliert ein qualifiziertes Beobachtungsfenster und nichts weiter. Wenn sich die beiden Binaries unterscheiden, wird ein stabiler Lauf als deskriptiver Vergleich etikettiert, mit der ausdrücklichen Notiz, dass die Verhältnisse Beobachtungen sind, keine statistische Signifikanz und keine Speedup-Behauptung. Die Disziplin zählt am meisten, wenn Sie gezielte Optimierungen validieren wie die in Profiling der PDF Library for Delphi und Ersatz von Hot Paths durch Hash-Indizes beschriebenen: Ein Profiler sagt Ihnen, wohin die Zeit fließt, aber nur ein kontrollierter gepaarter Lauf über echte Dokumente sagt Ihnen, ob die Änderung den Kontakt mit der gesamten Pipeline überlebt hat. Eine Grenze noch, die man laut aussprechen sollte — normal-save enthält das Laden, und das Peak-Working-Set, das der Läufer notiert, ist prozessweit, also steckt darin kein Speicher, der allein aufs Speichern entfällt
Drei Ausgabe-Sperren und eine Vier-Compiler-Matrix
Keine Zeitmessung der PDF Library for Delphi zählt, solange nicht die von ihr erzeugte Datei drei unabhängige Sperren passiert, denn ein Save, der schnell ein kaputtes PDF schreibt, ist kein schnellerer Save. Der Benchmark prüft zuerst, dass beide Operationen 1 zurückgaben und die zugelassene Seitenzahl meldeten, und validiert dann das einzelne gespeicherte PDF in dieser Reihenfolge:
- Struktur: Ein unabhängiger PDF-Checker muss die gespeicherte Datei ohne Fehler und Warnungen bestehen lassen
- Rendering: Jede Seite wird in ihrem Standardzustand gerendert, und das Per-Page-Image-SHA-256-Set muss exakt dem Referenz-Rendering der zugelassenen Quelle entsprechen
- Nichtvisuelle Semantik: Ein separater semantischer Vergleich gegen die Quelle deckt ausgewählte Eigenschaften ab, die Pixel nicht zeigen können, darunter Optional-Content- und Messstrukturen innerhalb ihres dokumentierten Umfangs
Mit diesen Sperren lief die komplette lokale Korpus-Matrix: der Probe auf FPC Win32, FPC Win64, Delphi Win32 und Delphi Win64 über 12 zugelassene PDFs mit 1.612 Quellseiten, also 48 Sample/Target-Paare und 6.448 validierte Ausgabeseiten ohne ausgewählte semantische Differenzen. Alle 96 Operationsmessungen behielten positive rohe Counter-Werte konsistent mit ihren gemeldeten Sekunden, und diese Werte werden bewusst nicht zu einer Cross-Compiler-Speed-Tabelle aggregiert, denn die Matrix ist funktionaler Beleg, kein kontrollierter Vergleich. Der Load/Save-Pfad behauptet auch nicht, jedes eingebettete Bild zu dekodieren, Signaturen zu validieren, XFA auszuführen oder PDF/UA zu zertifizieren; wenn Sie den Rendering-Durchsatz beurteilen wollen statt der Load/Save-Kosten, sind die Nebenläufigkeits-Constraints in Parallel-Page-Rendering und Thread-Safety in PDF Library for Delphi der bessere Startpunkt
Die praktische Takeaway ist kurz: Roh-Counter behalten, die Reihenfolge alternieren, den Start absperren, verrauschte Streuungen verweigern und niemals eine Ausgabe messen, die Sie nicht validiert haben. Diese Regeln ermöglichen es der PDF Library for Delphi, „keine messbare Änderung“ genauso selbstbewusst zu sagen wie „schneller“, und dieselbe Probe-Quelle kompiliert unverändert auf Delphi und FPC für Win32 und Win64. Die Bibliothek, ihre Load/Save-API und die unterstützten Compiler können Sie auf der Produktseite der PDF Library for Delphi ansehen