Desempenho
PDF Oxide v0.3.11 entrega 0.8ms de extração de texto em média em Python em um corpus de 3.830 PDFs — 5× mais rápido que PyMuPDF e 15× mais rápido que pypdf, com 100% de taxa de sucesso em PDFs válidos.
Resultados dos Benchmarks
Corpus: 3.830 PDFs
Três suítes de teste públicas independentes combinadas:
| Suíte | PDFs | Origem |
|---|---|---|
| veraPDF | 2.907 | Corpus de teste de conformidade PDF/A |
| Mozilla pdf.js | 897 | Suíte de teste de renderização de PDF do navegador |
| SafeDocs | 26 | Corpus de PDFs malformados do DARPA SafeDocs |
Por que este corpus é confiável
Esses não são PDFs escolhidos a dedo. Cada suíte é um corpus de teste estabelecido e revisado por pares, mantido por órgãos de padronização ou grandes projetos de código aberto:
-
veraPDF é o validador oficial de conformidade PDF/A usado pela comunidade de padrões PDF há mais de 10 anos. Seus 2.907 arquivos de teste são atômicos — cada um testa exatamente um recurso da especificação PDF — abrangendo todas as versões PDF/A (1A/1B, 2A/2B/2U, 3A/3B/3U, 4/4E/4F), acessibilidade PDF/UA (UA1, UA2), e ambos ISO 32000-1 (PDF 1.7) e ISO 32000-2 (PDF 2.0). Licenciado sob CC BY 4.0.
-
Mozilla pdf.js alimenta a renderização de PDF no Firefox, processando bilhões de PDFs anualmente. Seus 897 arquivos de teste cobrem casos extremos de renderização do mundo real: documentos complexos de múltiplas páginas, tipos de anotação (estilos de borda, destaques, marcações, anexos de arquivo), widgets de formulário, codificações de fonte incomuns, documentos grandes de teste de estresse e casos extremos de content stream. 7 arquivos são intencionalmente corrompidos para testar a rejeição correta de erros.
-
DARPA SafeDocs é um programa de pesquisa de segurança financiado pelo governo dos EUA focado na robustez de parsers. Seus 26 arquivos visam os casos extremos mais difíceis: ciclos de content stream em fontes Type3, trailers startxref duplos, sintaxe PDF compactada, variações de dialeto, casos extremos de imagens inline, aninhamento recursivo de fontes e PDFs criptografados com senhas Unicode (incluindo UTF-16 LE). Esses arquivos são projetados para travar, bloquear ou explorar parsers vulneráveis.
O que este corpus cobre
- Todas as principais versões de PDF: PDF 1.0 até PDF 2.0
- Criptografia e senhas: AES-256, RC4, senhas Unicode, codificação UTF-16 LE
- Casos extremos de segurança: Estruturas recursivas, ciclos de content stream, trailers malformados, corrupção gerada por fuzzing
- Diversidade de fontes: TrueType, CIDFont, Type1, Type3, codificações CJK, subconjuntos incorporados
- Complexidade de documentos: De fixtures de página única a documentos com mais de 10.000 páginas, imagens inline, Form XObjects aninhados
- Rejeição correta: 7 arquivos intencionalmente quebrados (cabeçalhos PDF ausentes, streams xref inválidos) — uma biblioteca que faz “parsing” destes é menos segura, não mais confiável
Dos 3.830 arquivos, 3.823 são PDFs válidos. Os 7 arquivos inválidos são fixtures de teste para tratamento de erros — PDF Oxide rejeita corretamente todos os 7.
Comparação de Bibliotecas Python
Tempo médio de extração de texto por PDF no corpus completo de 3.830 PDFs:
| Biblioteca | Média | p99 | Taxa de Sucesso | Licença |
|---|---|---|---|---|
| PDF Oxide | 0.8ms | 9ms | 100% | MIT |
| PyMuPDF | 4.6ms | 28ms | 99.3% | AGPL-3.0 |
| pypdfium2 | 4.1ms | 42ms | 99.2% | Apache-2.0 |
| pymupdf4llm | 55.5ms | 280ms | 99.1% | AGPL-3.0 |
| pdftext | 7.3ms | 82ms | 99.0% | GPL-3.0 |
| pdfminer | 16.8ms | 124ms | 98.8% | MIT |
| pdfplumber | 23.2ms | 189ms | 98.8% | MIT |
| markitdown | 108.8ms | 378ms | 98.6% | MIT |
| pypdf | 12.1ms | 97ms | 98.4% | BSD-3 |
PDF Oxide é a biblioteca PDF mais rápida disponível para Python. Diferente do PyMuPDF, usa a licença MIT — sem restrições AGPL para uso comercial.
Comparação de Bibliotecas Rust
| Biblioteca | Média | p99 | Taxa de Sucesso | Extração de Texto |
|---|---|---|---|---|
| PDF Oxide | 0.8ms | 9ms | 100% | Integrada, nível de produção |
| oxidize_pdf | 13.5ms | 11ms | 99.1% | Básica |
| unpdf | 2.8ms | 10ms | 95.1% | Básica |
| pdf_extract | 4.08ms | 37ms | 91.5% | Básica |
| lopdf | 0.3ms | 2ms | 80.2% | Sem extração integrada |
lopdf é mais rápido nos PDFs que consegue processar, mas falha em 20% do corpus. pdf_oxide é o único crate Rust que combina 100% de confiabilidade com extração de texto integrada. Note que lopdf não fornece extração de texto — você precisa construir decodificação de fontes e análise de espaçamento por conta própria.
Qualidade do Texto
PDF Oxide alcança 99,5% de paridade de texto comparado com PyMuPDF e pypdfium2 no corpus completo. A qualidade foi medida comparando a saída de texto extraído caractere por caractere em todos os 3.823 PDFs válidos.
Detalhamento por Corpus
| Corpus | PDFs | PDF Oxide Média | pypdfium2 Média | PyMuPDF Média |
|---|---|---|---|---|
| veraPDF | 2.907 | 0.7ms | 3.6ms | 4.1ms |
| Mozilla pdf.js | 897 | 1.1ms | 5.8ms | 6.2ms |
| SafeDocs | 26 | 0.9ms | 4.0ms | 4.3ms |
Histórico de Otimização: v0.3.5 → v0.3.8
A v0.3.8 eliminou dois gargalos críticos O(n) que fizeram a média cair de 23.3ms para 0.8ms (Python) no mesmo corpus.
1. Cache em Massa da Árvore de Páginas
Antes: get_page() percorria a árvore de páginas a partir da raiz para cada página não cacheada. Para extração sequencial de todas as páginas, isso era O(n) por página e O(n²) no total.
Depois: No primeiro acesso à página, toda a árvore de páginas é percorrida uma vez e todas as páginas são cacheadas em um HashMap<usize, Object>. Todo acesso subsequente é O(1).
Esta é a correção que trouxe um PDF de 10.000 páginas de 55 segundos para 332 milissegundos.
2. Cache de Offset de Scan-for-Object
Antes: Quando objetos estavam ausentes da tabela xref, scan_for_object() lia o arquivo PDF inteiro para cada objeto ausente. PDFs com tags e centenas de elementos de árvore de estrutura que não estavam no xref desencadeavam centenas de leituras completas do arquivo.
Depois: O arquivo é escaneado uma vez e todos os offsets de objetos são cacheados em um HashMap. Buscas subsequentes são O(1).
3. Extração de Texto em Passagem Única
Antes: extract_spans() executava duas passagens sobre o conteúdo da página — primeiro para classificar o tipo de documento (acadêmico, jornal, formulário, etc.), depois para extrair texto.
Depois: A passagem de classificação foi eliminada completamente. Limiares adaptativos conscientes de fonte agora produzem resultados iguais ou melhores em uma única passagem.
4. Pré-alocação de Content Stream
Antes: parse_content_stream() construía o Vec de operadores começando da capacidade padrão, causando realocações repetidas em content streams grandes.
Depois: O Vec é pré-alocado com base no tamanho do stream (data.len() / 20), que estima aproximadamente um operador a cada 20 bytes.
Metodologia
Todos os benchmarks usam a mesma metodologia:
- Cada biblioteca processa todos os 3.830 PDFs usando multiprocessing do Python (um PDF por processo)
- Timeout de 60 segundos por PDF — qualquer PDF que exceda isso é contado como falha
- O texto extraído é salvo em disco por biblioteca para comparação de qualidade
- Tempo de relógio medido da abertura do arquivo até a extração final do texto
- Sem execuções de aquecimento, sem cache entre arquivos
- Thread única por PDF
O harness de benchmark executa todas as 18 bibliotecas (3 Rust, 15 Python) na mesma máquina, mesmo corpus, mesmas condições.
Reproduzindo os Benchmarks
Os corpora de teste públicos estão disponíveis gratuitamente:
- veraPDF: github.com/veraPDF/veraPDF-corpus
- Mozilla pdf.js: github.com/mozilla/pdf.js/tree/master/test/pdfs
- SafeDocs: github.com/pdf-association/safedocs
Execute a verificação:
cargo run --release --example verify_corpus -- \
/path/to/veraPDF-corpus \
/path/to/pdfjs-test \
/path/to/safedocs \
--csv results.csv
Características de Desempenho
Onde PDF Oxide É Rápido
- Extração de texto: O principal alvo de otimização. Sub-milissegundo para documentos típicos.
- Extração sequencial de múltiplas páginas: O cache da árvore de páginas torna a extração de todas as páginas de um documento grande quase tão rápida quanto extrair uma.
- PDFs com tags: Travessia da árvore de estrutura e resolução de objetos agora são cacheadas.
- PDFs malformados: Parsing tolerante com estratégias de fallback evita retentativas caras.
O Que Escala Linearmente
- Contagem de páginas: Cada página é processada independentemente. 100 páginas leva aproximadamente 100x uma página.
- Tamanho do content stream: O parsing de operadores é linear no comprimento do stream.
- Extração de imagens: Proporcional ao número e tamanho das imagens.
Quando Esperar Resultados Mais Lentos
- PDFs escaneados com OCR: O processamento OCR (se habilitado) é significativamente mais lento que a extração de texto.
- Renderização: A renderização de páginas para imagens depende da complexidade do conteúdo e do DPI alvo.
- PDFs fortemente criptografados: A descriptografia AES-256 adiciona overhead por stream.
- PDFs com milhares de fontes: O parsing de fontes é cacheado por documento, mas o parsing inicial escala com a contagem de fontes.
Próximos Passos
- Changelog – histórico completo de versões
- Comparação com Bibliotecas Python – comparação detalhada com PyMuPDF, pypdf, pdfplumber, pdfminer
- Comparação com Bibliotecas Rust – comparação detalhada com lopdf, pdf_extract, pdf-rs
- Primeiros Passos com Rust – instalação e primeira extração
- Referência da API Rust – documentação completa da API