Skip to content

성능

PDF Oxide v0.3.11은 3,830개 PDF 코퍼스에서 Python 텍스트 추출 평균 0.8ms를 달성합니다. PyMuPDF보다 5배, pypdf보다 15배 빠르며, 유효한 PDF에 대해 100% 통과율을 기록합니다.

벤치마크 결과

코퍼스: 3,830개 PDF

세 개의 독립적인 공개 테스트 스위트를 결합했습니다:

스위트 PDF 수 출처
veraPDF 2,907 PDF/A 적합성 테스트 코퍼스
Mozilla pdf.js 897 브라우저 PDF 렌더링 테스트 스위트
SafeDocs 26 DARPA SafeDocs 비정상 PDF 코퍼스

이 코퍼스가 신뢰할 수 있는 이유

이 PDF들은 임의로 선별된 것이 아닙니다. 각 스위트는 표준 기관이나 주요 오픈소스 프로젝트가 관리하는 검증된 테스트 코퍼스입니다:

  • veraPDF는 PDF 표준 커뮤니티에서 10년 이상 사용해 온 공식 PDF/A 적합성 검증 도구입니다. 2,907개의 테스트 파일은 각각 하나의 PDF 사양 기능을 정확히 테스트하는 원자적 구성으로, 모든 PDF/A 버전(1A/1B, 2A/2B/2U, 3A/3B/3U, 4/4E/4F), PDF/UA 접근성(UA1, UA2), ISO 32000-1 (PDF 1.7)과 ISO 32000-2 (PDF 2.0)를 모두 포함합니다. CC BY 4.0 라이선스입니다.

  • Mozilla pdf.js는 Firefox에서 PDF 렌더링을 담당하며, 매년 수십억 개의 PDF를 처리합니다. 897개의 테스트 파일은 복잡한 다중 페이지 문서, 주석 유형(테두리 스타일, 하이라이트, 캐럿, 파일 첨부), 폼 위젯, 비정상적인 폰트 인코딩, 대용량 스트레스 테스트 문서, 콘텐츠 스트림 엣지 케이스 등 실제 렌더링 엣지 케이스를 다룹니다. 7개 파일은 올바른 오류 거부를 테스트하기 위해 의도적으로 손상되었습니다.

  • DARPA SafeDocs는 파서 견고성에 초점을 맞춘 미국 정부 지원 보안 연구 프로그램입니다. 26개 파일은 가장 어려운 엣지 케이스를 타겟팅합니다: Type3 폰트의 콘텐츠 스트림 순환, 이중 startxref 트레일러, 압축된 PDF 구문, 방언 변형, 인라인 이미지 엣지 케이스, 재귀적 폰트 중첩, 유니코드 비밀번호(UTF-16 LE 포함)를 사용한 암호화된 PDF 등입니다. 이러한 파일들은 취약한 파서를 충돌시키거나, 멈추게 하거나, 악용하도록 설계되었습니다.

이 코퍼스가 다루는 범위

  • 모든 주요 PDF 버전: PDF 1.0부터 PDF 2.0까지
  • 암호화 및 비밀번호: AES-256, RC4, 유니코드 비밀번호, UTF-16 LE 인코딩
  • 보안 엣지 케이스: 재귀 구조, 콘텐츠 스트림 순환, 비정상 트레일러, 퍼징 생성 손상
  • 폰트 다양성: TrueType, CIDFont, Type1, Type3, CJK 인코딩, 임베디드 서브셋
  • 문서 복잡도: 단일 페이지 픽스처부터 10,000+ 페이지 문서, 인라인 이미지, 중첩된 Form XObject
  • 올바른 거부: 의도적으로 손상된 7개 파일(누락된 PDF 헤더, 잘못된 xref 스트림) – 이러한 파일을 "파싱"하는 라이브러리는 덜 안전한 것이지, 더 신뢰할 수 있는 것이 아닙니다

3,830개 파일 중 3,823개가 유효한 PDF입니다. 7개의 유효하지 않은 파일은 에러 처리를 위한 테스트 픽스처이며, PDF Oxide는 7개 모두를 올바르게 거부합니다.

Python 라이브러리 비교

전체 3,830개 PDF 코퍼스에서의 PDF당 평균 텍스트 추출 시간:

라이브러리 평균 p99 통과율 라이선스
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는 현재 사용 가능한 가장 빠른 Python PDF 라이브러리입니다. PyMuPDF와 달리 MIT 라이선스를 사용하므로, 상용 소프트웨어에서도 AGPL 제약 없이 사용할 수 있습니다.

Rust 라이브러리 비교

라이브러리 평균 p99 통과율 텍스트 추출
PDF Oxide 0.8ms 9ms 100% 내장, 프로덕션 수준
oxidize_pdf 13.5ms 11ms 99.1% 기본
unpdf 2.8ms 10ms 95.1% 기본
pdf_extract 4.08ms 37ms 91.5% 기본
lopdf 0.3ms 2ms 80.2% 내장 추출 기능 없음

lopdf는 파싱할 수 있는 PDF에서는 더 빠르지만, 코퍼스의 20%에서 실패합니다. pdf_oxide는 100% 신뢰성과 내장 텍스트 추출을 결합한 유일한 Rust 크레이트입니다. lopdf는 텍스트 추출 기능을 제공하지 않으므로, 폰트 디코딩과 간격 분석을 직접 구현해야 합니다.

텍스트 품질

PDF Oxide는 전체 코퍼스에서 PyMuPDF 및 pypdfium2와 비교하여 99.5%의 텍스트 동등성을 달성합니다. 품질은 3,823개의 유효한 PDF 전체에서 추출된 텍스트 출력을 문자 단위로 비교하여 측정했습니다.

코퍼스별 분석

코퍼스 PDF 수 PDF Oxide 평균 pypdfium2 평균 PyMuPDF 평균
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

최적화 이력: v0.3.5 -> v0.3.8

v0.3.8에서는 두 가지 핵심적인 O(n) 병목 현상을 제거하여 동일한 코퍼스에서 평균이 23.3ms에서 0.8ms(Python)로 감소했습니다.

1. 페이지 트리 일괄 캐시

이전: get_page()는 캐시되지 않은 모든 페이지에 대해 루트부터 페이지 트리를 순회했습니다. 전체 페이지를 순차적으로 추출할 경우, 페이지당 O(n), 전체 O(n^2)이었습니다.

이후: 첫 번째 페이지 접근 시 전체 페이지 트리를 한 번 순회하고, 모든 페이지를 HashMap<usize, Object>에 캐시합니다. 이후 모든 접근은 O(1)입니다.

이 수정으로 10,000페이지 PDF가 55초에서 332밀리초로 단축되었습니다.

2. 객체 오프셋 스캔 캐시

이전: xref 테이블에 객체가 없을 경우, scan_for_object()는 누락된 각 객체에 대해 전체 PDF 파일을 읽었습니다. 수백 개의 구조 트리 요소가 xref에 없는 태그된 PDF에서 수백 번의 전체 파일 읽기가 발생했습니다.

이후: 파일을 한 번 스캔하고 모든 객체 오프셋을 HashMap에 캐시합니다. 이후 조회는 O(1)입니다.

3. 단일 패스 텍스트 추출

이전: extract_spans()는 페이지 콘텐츠에 대해 두 번의 패스를 실행했습니다. 첫 번째는 문서 유형(학술 논문, 신문, 양식 등)을 분류하고, 두 번째는 텍스트를 추출했습니다.

이후: 분류 패스를 완전히 제거했습니다. 적응형 폰트 인식 임계값이 단일 패스에서 동등하거나 더 나은 결과를 생성합니다.

4. 콘텐츠 스트림 사전 할당

이전: parse_content_stream()은 기본 용량부터 시작하여 연산자 Vec을 구축했으며, 대용량 콘텐츠 스트림에서 반복적인 재할당이 발생했습니다.

이후: 스트림 크기를 기반으로 Vec를 사전 할당합니다(data.len() / 20). 이는 20바이트당 대략 하나의 연산자를 추정한 것입니다.

방법론

모든 벤치마크는 동일한 방법론을 사용합니다:

  1. 각 라이브러리는 Python multiprocessing을 사용하여 모든 3,830개 PDF를 처리합니다 (프로세스당 하나의 PDF)
  2. PDF당 60초 타임아웃 – 이를 초과하는 PDF는 실패로 처리합니다
  3. 추출된 텍스트는 품질 비교를 위해 라이브러리별로 디스크에 저장합니다
  4. 파일 열기부터 최종 텍스트 추출까지의 벽시계 시간을 측정합니다
  5. 워밍업 실행 없음, 파일 간 캐싱 없음
  6. PDF당 단일 스레드

벤치마크 하네스는 동일한 머신, 동일한 코퍼스, 동일한 조건에서 모든 18개 라이브러리(Rust 3개, Python 15개)를 실행합니다.

벤치마크 재현

공개 테스트 코퍼스는 자유롭게 이용 가능합니다:

검증 실행:

cargo run --release --example verify_corpus -- \
    /path/to/veraPDF-corpus \
    /path/to/pdfjs-test \
    /path/to/safedocs \
    --csv results.csv

성능 특성

PDF Oxide가 빠른 영역

  • 텍스트 추출: 주요 최적화 대상입니다. 일반적인 문서에서 서브 밀리초 성능을 제공합니다.
  • 순차적 다중 페이지 추출: 페이지 트리 캐시 덕분에 대용량 문서에서 전체 페이지를 추출하는 것이 단일 페이지 추출과 거의 같은 속도입니다.
  • 태그된 PDF: 구조 트리 순회와 객체 해석이 캐시됩니다.
  • 비정상 PDF: 폴백 전략을 사용한 관대한 파싱으로 비용이 큰 재시도를 방지합니다.

선형으로 확장되는 영역

  • 페이지 수: 각 페이지는 독립적으로 처리됩니다. 100페이지는 대략 1페이지의 100배가 소요됩니다.
  • 콘텐츠 스트림 크기: 연산자 파싱은 스트림 길이에 선형적입니다.
  • 이미지 추출: 이미지의 수와 크기에 비례합니다.

느린 결과를 예상해야 하는 경우

  • OCR이 포함된 스캔 PDF: OCR 처리(활성화된 경우)는 텍스트 추출보다 현저히 느립니다.
  • 렌더링: 페이지를 이미지로 렌더링하는 것은 콘텐츠 복잡도와 목표 DPI에 따라 달라집니다.
  • 강력하게 암호화된 PDF: AES-256 복호화는 스트림당 오버헤드를 추가합니다.
  • 수천 개의 폰트가 포함된 PDF: 폰트 파싱은 문서당 캐시되지만, 초기 파싱은 폰트 수에 비례합니다.

다음 단계