Um benchmark honesto de load/save da PDF Library for Delphi cronometra LoadFromFile e SaveToFile com QueryPerformanceCounter, guarda os ticks crus e a frequência do contador, roda baseline e candidato em pares alternados A/B, B/A, A/B, se recusa a começar enquanto o load de CPU está acima de 25%, rejeita qualquer resultado cuja dispersão range/mediana passe de 15%, e joga fora toda medição cujo PDF salvo falhe em validação estrutural, de renderização ou semântica. A lista parece burocracia até a primeira vez que uma alegação de "20% faster" evapora num rerun. O que vem a seguir é como a sonda dedicada de corpus e seu runner de comparação chegaram lá, incluindo a rodada em que a máquina estava simplesmente ocupada demais para medir qualquer coisa e o harness disse isso corretamente
Por que um benchmark de PDF em Delphi reporta zero segundos?
Um benchmark de load de PDF reporta zero segundos quando o clock dele tica mais grosseiramente que a operação que ele mede, e o GetTickCount64 é exatamente esse tipo de clock: ele retorna milissegundos, mas no Windows só avança quando a interrupção do timer de sistema dispara, comumente a cada 15,6 ms. É a escolha óbvia no FPC porque o TStopwatch não está disponível nessa toolchain, e um timer construído sobre ele que registra o tempo decorrido com três casas decimais parece preciso. Carregar um desenho CAD pequeno ou um documento tagged curto termina bem dentro de um passo do timer, então esse tipo de timer pode imprimir 0.000 para um load que claramente fez trabalho de verdade
function ElapsedSeconds(StartTick: QWord): Double;
begin
Result:= (GetTickCount64- StartTick)/ 1000.0;
end;
// dentro do loop de operações
Lib:= TPDFlib.Create;
try
Lib.OnProgress:= Reporter.Progress;
Started:= GetTickCount64;
LoadCode:= Lib.LoadFromFile(InputFile, Password);
...
Um zero é pior que um número impreciso, porque toda comparação que você construir sobre ele divide por ele. O runner de comparação pareada trata qualquer braço com mínimo zero como inconclusivo, com o motivo "Zero duration prevents a meaningful ratio", que é a recusa correta, mas também significa que um timer de contagem de ticks deixa uma lacuna de medição exatamente onde os arquivos curtos vivem. Um loop de demo que também instala um callback OnProgress inclui o overhead do callback nas medições, algo que uma medição limpa de load/save não deveria carregar, então números de demo não são intercambiáveis com uma sonda dedicada
Cronometrando LoadFromFile e SaveToFile com QueryPerformanceCounter
A sonda de console dedicada, Tests/CorpusLoadSave.dpr, mede duas operações por arquivo de entrada com QueryPerformanceCounter: LoadFromFile mais a leitura do PageCount, e LoadFromFile mais PageCount mais SaveToFile. Cada operação recebe uma instância TPDFlib fresca e nenhum callback de progresso, e o construtor e o destrutor da instância ficam fora da região cronometrada, assim como a escrita do CSV e toda a validação de output. O contador é lido imediatamente antes do load e imediatamente depois da última chamada da biblioteca, e o LastErrorCode é buscado só depois da segunda leitura
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');
A sonda escreve a contagem de ticks crua e a frequência do contador ao lado dos segundos derivados, formatados com nove casas decimais e um separador decimal fixo ., para que qualquer um consiga recomputar o quociente a partir do CSV em vez de confiar nele. Na build FPC Win64 a amostra CAD carregou em 8.888 ticks a 10.000.000 de ticks por segundo, registrados como 0.000888800 segundos — uma observação que um timer de contagem de ticks arredondaria para zero. A sonda deliberadamente não corta valores curtos, não substitui por uma duração mínima, nem subtrai um overhead estimado do timer, e ainda escreve as duas linhas com exit code não zero quando uma chamada da biblioteca falha. Nove dígitos não são exatidão, porém: mais precisão registrada não diz nada sobre repetibilidade, e observações ruidosas ou zero ainda precisam ser rejeitadas mais à frente
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));
O que torna uma comparação de tempos de load/save de PDF confiável?
Uma comparação de tempos entre duas builds da PDF Library for Delphi só é confiável quando a ordem de início, as condições de início e a dispersão estão todas controladas e registradas, então o runner de comparação agenda pelo menos três pares na ordem A/B, B/A, A/B. Rodar sempre o baseline primeiro entrega discretamente ao candidato um cache de arquivo mais quente e um estado térmico diferente; alternar a ordem espalha esse viés pelos dois braços em vez de creditá-lo a um só. Antes de cada braço o runner calcula o hash SHA-256 do arquivo de entrada completo, o que ao mesmo tempo verifica que nada mudou e pré-lê os mesmos bytes para qualquer um dos braços, e ele calcula de novo o hash dos dois executáveis e das ferramentas de validação após cada rodada, para que um binário rebuildado não se esgueire no meio de uma série
O runner então amostra a utilização de CPU da máquina inteira uma vez por segundo e só começa o braço quando uma amostra cai para 25% ou menos, esperando no máximo 30 segundos antes de registrar a tentativa como rejeitada. Esse gate controla a condição de início e nada mais: ele não isola a máquina durante a rodada, e estado de energia, thermal throttling, trabalho em background e caching do SO ainda podem mover os números. Por isso o segundo filtro é estatístico no sentido mais simples. Para cada operação o runner calcula range dividido pela mediana do braço baseline, do braço candidato e da distribuição das razões pareadas candidato/baseline, e se qualquer um dos três passar de 0,15 o resultado é rotulado de ruidoso em vez de reportado como achado
Por que um controle same-binary prova repetibilidade, não velocidade?
Um controle same-binary roda executáveis idênticos como baseline e candidato, então uma razão perto de 1,0 só pode provar que o setup de medição se repete; ele nunca pode mostrar que uma implementação ficou mais rápida. O primeiro controle estrito, em 2026-09-21, usou a sonda FPC Win64 de alta resolução contra um guia tagged admitido de 70 páginas, e os seis inícios foram rejeitados porque as amostras de CPU variaram de 26,5% a 93,8%. O relatório continha falhas e nenhum agregado, que é exatamente o desfecho que você quer quando a máquina está ocupada. Uma nova tentativa no mesmo dia, com inputs byte-idênticos, o mesmo executável da sonda e limiares inalterados, aceitou os seis inícios em menos de 3 segundos; toda dispersão range/mediana ficou entre 0,019 e 0,054, e as medianas das razões foram 1,0084 para LoadFromFile e 0,9872 para LoadFromFile + SaveToFile
Esse par de números estabelece uma janela de observação qualificada e nada mais. Quando os dois binários diferem, uma rodada estável é rotulada como comparação descritiva, com a nota explícita de que as razões são observações, não significância estatística nem alegação de speedup. A disciplina importa mais quando você está validando otimizações direcionadas como as descritas em fazer profiling da PDF Library for Delphi e trocar hot paths por hash indexes: um profiler te diz para onde o tempo vai, mas só uma rodada pareada controlada em documentos reais te diz se a mudança sobreviveu ao contato com o pipeline inteiro. Mais uma fronteira que vale dizer em voz alta — o normal-save inclui o load, e o pico de working set que o runner registra é do processo inteiro, então nada disso é memória atribuível só ao save
Três gates de output e uma matriz de quatro compiladores
Nenhuma medição da PDF Library for Delphi conta se o arquivo que ela produziu não passar em três gates independentes, porque um save que escreve um PDF quebrado rapidamente não é um save mais rápido. O benchmark primeiro confere que as duas operações retornaram 1 e reportaram a contagem de páginas admitida, depois valida o único PDF salvo nesta ordem:
- Estrutura: um checker de PDF independente precisa passar o arquivo salvo sem erros nem warnings
- Renderização: cada página é renderizada no estado default dela, e o conjunto de SHA-256 de imagem por página precisa casar exatamente com a renderização de referência da fonte admitida
- Semântica não visual: uma comparação semântica separada contra a fonte cobre propriedades selecionadas que pixels não conseguem mostrar, incluindo estruturas de optional-content e de medição dentro do escopo documentado delas
Com esses gates no lugar, a matriz completa do corpus local rodou a sonda em FPC Win32, FPC Win64, Delphi Win32 e Delphi Win64 sobre 12 PDFs admitidos com 1.612 páginas de fonte, dando 48 pares amostra/target e 6.448 páginas de output validadas sem diferenças semânticas selecionadas. As 96 medições de operação mantiveram valores crus de contador positivos e consistentes com os segundos reportados, e esses valores deliberadamente não são agregados numa tabela de velocidade entre compiladores, porque a matriz é evidência funcional, não uma comparação controlada. O caminho de load/save também não alega decodificar toda imagem embutida, validar assinaturas, executar XFA ou certificar PDF/UA; se você precisa julgar throughput de renderização em vez de custo de load/save, as restrições de concorrência em renderização paralela de páginas e thread safety na PDF Library for Delphi são o melhor ponto de partida
A lição prática é curta: guarde os contadores crus, alterne a ordem, filtre o início, recuse dispersões ruidosas, e nunca cronometre um output que você não validou. Essas regras são o que permitem à PDF Library for Delphi dizer "no measurable change" com a mesma confiança que "faster", e o mesmo fonte da sonda compila sem alteração em Delphi e FPC para Win32 e Win64. Você pode conferir a biblioteca, a API de load/save dela e os compiladores suportados na página de produto da PDF Library for Delphi