Skip to content

Производительность

PDF Oxide v0.3.11 обеспечивает среднее время извлечения текста 0.8 мс в Python на корпусе из 3 830 PDF-файлов — в 5 раз быстрее PyMuPDF и в 15 раз быстрее pypdf, при 100% успешной обработке валидных PDF.

Результаты benchmark

Корпус: 3 830 PDF-файлов

Объединены три независимых публичных набора тестов:

Набор PDF-файлов Источник
veraPDF 2 907 Тестовый корпус соответствия PDF/A
Mozilla pdf.js 897 Тестовый набор рендеринга PDF в браузере
SafeDocs 26 Корпус некорректных PDF от DARPA SafeDocs

Почему этот корпус надёжен

Это не подобранные вручную PDF. Каждый набор является признанным, прошедшим экспертную оценку тестовым корпусом, который поддерживается организациями по стандартизации или крупными проектами с открытым исходным кодом:

  • veraPDF — официальный валидатор соответствия PDF/A, используемый сообществом стандартов PDF более 10 лет. Его 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 обеспечивает рендеринг PDF в Firefox, обрабатывая миллиарды PDF-файлов ежегодно. Его 897 тестовых файлов покрывают реальные граничные случаи рендеринга: сложные многостраничные документы, типы аннотаций (стили границ, выделения, каретки, вложенные файлы), виджеты форм, необычные кодировки шрифтов, объёмные стресс-тесты и граничные случаи потоков содержимого. 7 файлов намеренно повреждены для проверки корректной обработки ошибок.

  • DARPA SafeDocs — финансируемая правительством США программа исследований безопасности, направленная на устойчивость парсеров. Её 26 файлов нацелены на самые сложные граничные случаи: циклы потоков содержимого в шрифтах Type3, двойные трейлеры startxref, компактный синтаксис PDF, вариации диалектов, граничные случаи инлайн-изображений, рекурсивное вложение шрифтов и зашифрованные PDF с паролями Unicode (включая UTF-16 LE). Эти файлы спроектированы так, чтобы вызывать сбои, зависания или эксплуатировать уязвимые парсеры.

Что покрывает этот корпус

  • Все основные версии PDF: PDF 1.0 — PDF 2.0
  • Шифрование и пароли: AES-256, RC4, пароли Unicode, кодировка 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-библиотек

Среднее время извлечения текста из одного PDF на полном корпусе из 3 830 файлов:

Библиотека Среднее p99 Успешность Лицензия
PDF Oxide 0.8 мс 9 мс 100% MIT
PyMuPDF 4.6 мс 28 мс 99.3% AGPL-3.0
pypdfium2 4.1 мс 42 мс 99.2% Apache-2.0
pymupdf4llm 55.5 мс 280 мс 99.1% AGPL-3.0
pdftext 7.3 мс 82 мс 99.0% GPL-3.0
pdfminer 16.8 мс 124 мс 98.8% MIT
pdfplumber 23.2 мс 189 мс 98.8% MIT
markitdown 108.8 мс 378 мс 98.6% MIT
pypdf 12.1 мс 97 мс 98.4% BSD-3

PDF Oxide — самая быстрая из доступных Python-библиотек для работы с PDF. В отличие от PyMuPDF, она использует лицензию MIT — без ограничений AGPL для коммерческого использования.

Сравнение Rust-библиотек

Библиотека Среднее p99 Успешность Извлечение текста
PDF Oxide 0.8 мс 9 мс 100% Встроенное, production-уровня
oxidize_pdf 13.5 мс 11 мс 99.1% Базовое
unpdf 2.8 мс 10 мс 95.1% Базовое
pdf_extract 4.08 мс 37 мс 91.5% Базовое
lopdf 0.3 мс 2 мс 80.2% Без встроенного извлечения

lopdf быстрее на тех PDF, которые он способен обработать, но отказывает на 20% корпуса. pdf_oxide — единственный Rust-крейт, который сочетает 100% надёжность со встроенным извлечением текста. Обратите внимание, что lopdf не предоставляет извлечения текста — декодирование шрифтов и анализ межсимвольных интервалов придётся реализовывать самостоятельно.

Качество текста

PDF Oxide достигает 99.5% совпадения текста по сравнению с PyMuPDF и pypdfium2 на полном корпусе. Качество измерялось посимвольным сравнением извлечённого текста по всем 3 823 валидным PDF.

Детализация по корпусам

Корпус PDF-файлов Среднее PDF Oxide Среднее pypdfium2 Среднее PyMuPDF
veraPDF 2 907 0.7 мс 3.6 мс 4.1 мс
Mozilla pdf.js 897 1.1 мс 5.8 мс 6.2 мс
SafeDocs 26 0.9 мс 4.0 мс 4.3 мс

История оптимизаций: v0.3.5 -> v0.3.8

В версии 0.3.8 были устранены два критических узких места O(n), которые позволили снизить среднее время с 23.3 мс до 0.8 мс (Python) на том же корпусе.

1. Массовый кеш дерева страниц

До: get_page() обходил дерево страниц от корня для каждой некешированной страницы. При последовательном извлечении всех страниц это давало O(n) на страницу и O(n^2) суммарно.

После: При первом обращении к странице всё дерево страниц обходится один раз, и все страницы кешируются в HashMap<usize, Object>. Каждый последующий доступ выполняется за O(1).

Именно эта оптимизация позволила обработать 10 000-страничный PDF за 332 мс вместо 55 секунд.

2. Кеш смещений объектов при сканировании

До: Когда объекты отсутствовали в таблице xref, scan_for_object() считывал весь PDF-файл для каждого отсутствующего объекта. PDF с тегами и сотнями элементов дерева структуры, отсутствующих в xref, провоцировали сотни полных считываний файла.

После: Файл сканируется один раз, и все смещения объектов кешируются в HashMap. Последующие обращения выполняются за O(1).

3. Однопроходное извлечение текста

До: extract_spans() выполнял два прохода по содержимому страницы — сначала классификацию типа документа (научная статья, газета, форма и т.д.), затем извлечение текста.

После: Проход классификации полностью исключён. Адаптивные пороги с учётом шрифтов теперь дают такие же или лучшие результаты за один проход.

4. Предварительное выделение памяти для потока содержимого

До: parse_content_stream() начинал формирование Vec операторов с ёмкости по умолчанию, что вызывало многократные перевыделения памяти на больших потоках содержимого.

После: Vec создаётся с предварительно рассчитанной ёмкостью на основе размера потока (data.len() / 20), что соответствует примерно одному оператору на 20 байт.

Методология

Все benchmark используют единую методологию:

  1. Каждая библиотека обрабатывает все 3 830 PDF с помощью Python multiprocessing (один PDF на процесс)
  2. Таймаут 60 секунд на PDF — превышение засчитывается как ошибка
  3. Извлечённый текст сохраняется на диск для каждой библиотеки для сравнения качества
  4. Измеряется реальное время от открытия файла до завершения извлечения текста
  5. Без прогревочных запусков, без кеширования между файлами
  6. Один поток на PDF

Тестовый стенд запускает все 18 библиотек (3 на Rust, 15 на Python) на одной машине, одном корпусе, в одинаковых условиях.

Воспроизведение benchmark

Публичные тестовые корпуса находятся в свободном доступе:

Запуск верификации:

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 страниц занимают примерно в 100 раз больше, чем одна.
  • Размер потока содержимого: парсинг операторов линеен по длине потока.
  • Извлечение изображений: пропорционально количеству и размеру изображений.

Когда ожидать замедления

  • Отсканированные PDF с OCR: обработка OCR (при включении) значительно медленнее извлечения текста.
  • Рендеринг: рендеринг страниц в изображения зависит от сложности содержимого и целевого DPI.
  • Сильно зашифрованные PDF: дешифрование AES-256 добавляет накладные расходы на каждый поток.
  • PDF с тысячами шрифтов: парсинг шрифтов кешируется в рамках документа, но начальный парсинг масштабируется с количеством шрифтов.

Дальнейшие шаги