Un benchmark honesto de carga y guardado para PDF Library for Delphi mide LoadFromFile y SaveToFile con QueryPerformanceCounter, conserva los ticks crudos y la frecuencia del contador, ejecuta baseline y candidate en pares alternados A/B, B/A, A/B, se niega a arrancar mientras la carga de CPU esté por encima del 25%, rechaza cualquier resultado cuyo spread entre rango y mediana supere el 15%, y tira a la basura toda medición cuyo PDF guardado no pase la validación estructural, de renderizado o semántica. La lista suena a burocracia hasta la primera vez que una afirmación de «20% más rápido» se evapora al repetir la prueba. Lo que sigue es cómo llegaron hasta ahí la sonda dedicada de corpus y su runner de comparación, incluida la ejecución en que la máquina estaba sencillamente demasiado ocupada para medir nada y el harness lo dijo correctamente
¿Por qué un benchmark de PDF en Delphi informa de cero segundos?
Un benchmark de carga de PDF informa de cero segundos cuando su reloj hace tick de forma más gruesa que la operación que mide, y GetTickCount64 es justo ese tipo de reloj: devuelve milisegundos, pero en Windows solo avanza cuando salta la interrupción del temporizador del sistema, normalmente cada 15,6 ms. El port FPC del demo de benchmark de archivos grandes de PDF Library for Delphi lo usaba porque TStopwatch no está disponible en esa toolchain, y registraba el tiempo transcurrido con tres decimales. Cargar un dibujo CAD pequeño o un documento tagged corto termina bien dentro de un paso del temporizador, así que el demo a veces imprimía 0.000 para una carga que claramente hizo trabajo de verdad
function ElapsedSeconds(StartTick: QWord): Double;
begin
Result:= (GetTickCount64- StartTick)/ 1000.0;
end;
// dentro del bucle de operaciones
Lib:= TPDFlib.Create;
try
Lib.OnProgress:= Reporter.Progress;
Started:= GetTickCount64;
LoadCode:= Lib.LoadFromFile(InputFile, Password);
...
Un cero es peor que un número impreciso, porque toda comparación que monte encima divide por él. El runner de comparación emparejada trata cualquier brazo con mínimo cero como inconcluyente con el motivo «Zero duration prevents a meaningful ratio», que es la negativa correcta, pero también significa que las mediciones del demo dejaban un hueco justo donde viven los archivos cortos. El mismo demo instala además un callback OnProgress, así que sus tiempos incluyen la sobrecarga del callback, que una medición limpia de carga y guardado no debería llevar, y las cifras archivadas del demo no son intercambiables con nada medido después
Medir LoadFromFile y SaveToFile con QueryPerformanceCounter
La sonda de consola dedicada, Tests/CorpusLoadSave.dpr, mide dos operaciones por archivo de entrada con QueryPerformanceCounter: LoadFromFile más la lectura de PageCount, y LoadFromFile más PageCount más SaveToFile. Cada operación recibe una instancia TPDFlib nueva y ningún callback de progreso, y el constructor y el destructor de la instancia quedan fuera de la región medida, igual que la escritura del CSV y toda la validación de salida. El contador se lee justo antes de la carga y justo después de la última llamada a la librería, y LastErrorCode se consulta solo tras la segunda lectura
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');
La sonda escribe el conteo de ticks crudo y la frecuencia del contador junto a los segundos derivados, formateados con nueve decimales y un separador decimal . fijo, para que cualquiera pueda recalcular el cociente desde el CSV en vez de fiarse de él. En el build FPC Win64 la muestra CAD cargó en 8.888 ticks a 10.000.000 de ticks por segundo, registrado como 0.000888800 segundos, una observación que el viejo temporizador habría redondeado a cero. La sonda deliberadamente no recorta valores cortos, no sustituye una duración mínima ni resta una sobrecarga estimada del temporizador, y sigue escribiendo ambas filas con código de salida distinto de cero cuando una llamada a la librería falla. Eso sí, nueve dígitos no son exactitud: más precisión registrada no dice nada sobre la repetibilidad, y las observaciones ruidosas o nulas siguen teniendo que rechazarse aguas abajo
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));
¿Qué hace fiable una comparación de tiempos de carga y guardado?
Una comparación de tiempos entre dos builds de PDF Library for Delphi solo es fiable cuando el orden de arranque, las condiciones de arranque y el spread están controlados y registrados, así que el runner de comparación programa al menos tres pares en orden A/B, B/A, A/B. Ejecutar siempre el baseline primero le entrega en bandeja al candidate una caché de archivos más caliente y un estado térmico distinto; alternar el orden reparte ese sesgo entre los dos brazos en lugar de acreditarlo a uno. Antes de cada brazo el runner calcula el SHA-256 del archivo de entrada completo, lo que a la vez verifica que no cambió nada y prelee los mismos bytes para cualquier brazo, y vuelve a hashear los dos ejecutables y las herramientas de validación después de cada ejecución para que un binario reconstruido no pueda colarse en medio de una serie
Después el runner muestrea la utilización de CPU de toda la máquina una vez por segundo y arranca el brazo solo cuando una muestra baja al 25% o menos, esperando como máximo 30 segundos antes de registrar el intento como rechazado. Esa compuerta controla la condición de arranque y nada más: no aísla la máquina durante la ejecución, y el estado de energía, el thermal throttling, el trabajo en segundo plano y la caché del sistema operativo todavía pueden mover los números. Así que el segundo filtro es estadístico en el sentido más llano. Para cada operación el runner calcula rango dividido por mediana para el brazo baseline, el brazo candidate y la distribución de ratios emparejados candidate/baseline, y si cualquiera de los tres supera 0.15 el resultado se etiqueta como ruidoso en vez de reportarse como hallazgo
¿Por qué un control de binario idéntico prueba repetibilidad y no velocidad?
Un control de binario idéntico ejecuta los mismos ejecutables como baseline y candidate, así que un ratio cercano a 1.0 solo puede probar que el montaje de medición se repite a sí mismo; nunca puede mostrar que una implementación se hizo más rápida. El primer control estricto del 2026-09-21 usó la sonda FPC Win64 de alta resolución contra una guía tagged admitida de 70 páginas, y los seis arranques fueron rechazados porque las muestras de CPU iban del 26,5% al 93,8%. El informe contenía fallos y ningún agregado, que es exactamente el desenlace que usted quiere cuando la máquina está ocupada. Un reintento el mismo día con entradas byte-idénticas, el mismo ejecutable de sonda y umbrales sin cambiar aceptó los seis arranques en menos de 3 segundos; cada spread rango-mediana quedó entre 0.019 y 0.054, y las medianas de los ratios fueron 1.0084 para LoadFromFile y 0.9872 para LoadFromFile + SaveToFile
Ese par de números establece una ventana de observación calificada y nada más. Cuando los dos binarios difieren, una ejecución estable se etiqueta como comparación descriptiva, con la nota explícita de que los ratios son observaciones, no significancia estadística ni una afirmación de speedup. La disciplina importa más cuando valida optimizaciones dirigidas como las descritas en perfilar PDF Library for Delphi y sustituir hot paths por hash indexes: un profiler le dice a dónde va el tiempo, pero solo una ejecución emparejada controlada sobre documentos reales le dice si el cambio sobrevivió al contacto con todo el pipeline. Un límite más que merece decirse en voz alta: normal-save incluye la carga, y el pico de working set que registra el runner es de todo el proceso, así que nada de eso es memoria atribuible solo al guardado
Tres compuertas de salida y una matriz de cuatro compiladores
Ninguna medición de PDF Library for Delphi cuenta si el archivo que produjo no pasa tres compuertas independientes, porque un guardado que escribe un PDF roto rápido no es un guardado más rápido. El benchmark comprueba primero que ambas operaciones devolvieron 1 e informaron del número de páginas admitido, y después valida el único PDF guardado en este orden:
- Estructura: un checker de PDF independiente debe dar el archivo guardado por bueno sin errores ni avisos
- Renderizado: cada página se renderiza en su estado por defecto, y el conjunto de SHA-256 de imagen por página debe coincidir exactamente con el renderizado de referencia de la fuente admitida
- Semántica no visual: una comparación semántica aparte contra la fuente cubre propiedades seleccionadas que los píxeles no pueden mostrar, incluidas las estructuras de optional-content y de medición dentro de su alcance documentado
Con esas compuertas en su sitio, la matriz completa del corpus local ejecutó la sonda en FPC Win32, FPC Win64, Delphi Win32 y Delphi Win64 sobre 12 PDFs admitidos con 1.612 páginas fuente, dando 48 pares sample/target y 6.448 páginas de salida validadas sin diferencias semánticas seleccionadas. Las 96 mediciones de operación conservaron valores crudos del contador positivos y coherentes con sus segundos reportados, y esos valores deliberadamente no se agregan en una tabla de velocidad entre compiladores, porque la matriz es evidencia funcional y no una comparación controlada. El camino de carga y guardado tampoco pretende decodificar cada imagen incrustada, validar firmas, ejecutar XFA ni certificar PDF/UA; si lo que necesita juzgar es el throughput de renderizado y no el coste de carga y guardado, las restricciones de concurrencia de renderizado paralelo de páginas y thread safety en PDF Library for Delphi son el mejor punto de partida
La conclusión práctica es corta: conserve los contadores crudos, alterne el orden, ponga compuerta al arranque, rechace los spreads ruidosos y nunca mida una salida que no haya validado. Esas reglas son las que permiten a PDF Library for Delphi decir «sin cambio medible» con la misma confianza que «más rápido», y el mismo código fuente de la sonda compila sin cambios en Delphi y FPC para Win32 y Win64. Puede revisar la librería, su API de carga y guardado y los compiladores soportados en la página de producto de PDF Library for Delphi