Память и потоковая запись
Spec: ISO 32000-2, §7.5.4 ISO 32000-2 §7.5.4 Evidence: Mixed evidence
Большой PDF не должен требовать большой кучи. На этой странице объясняется, как NextPDF удерживает потребление памяти процесса в заданных пределах по мере роста документа, где выполняет потоковую запись на диск вместо накопления в памяти и что здесь означает «бюджет производительности»: проверяемый контракт, а не громкая цифра.
Почему это важно
Заголовок раздела «Почему это важно»Формат PDF не вынуждает генератор использовать большую кучу. Его таблица перекрёстных ссылок записывает байтовое смещение для каждого косвенного объекта, поэтому средству чтения нужен произвольный доступ к файлу, а не весь файл в памяти. Генератор может следовать этой модели: выдавать объекты по мере их завершения и запоминать только то, куда они были записаны. Если же весь документ остаётся в куче до финальной записи, расход памяти растёт линейно с количеством страниц, и отчёт, который нормально работает при ста страницах, может уронить процесс при пятидесяти тысячах.
Для пакетных и фоновых нагрузок это разница между стабильным сервисом и сервисом, который непредсказуемо отказывает под нагрузкой. Ограниченное потребление памяти — это проектное свойство: его нужно реализовать, а не надеяться на удачное число.
Если коротко
Заголовок раздела «Если коротко»- Потоковый писатель устроен так, чтобы потребление памяти оставалось ограниченным в расчёте на документ. Каждая страница записывается в вывод сразу после завершения. Затем её буфер освобождается.
- Служебные данные, которые иначе росли бы вместе с количеством объектов — смещения перекрёстных ссылок и ссылки
Kidsдерева страниц — записываются во временные потоки, открытые с помощьюphp://temp/maxmemory:0. Эти потоки сразу сбрасываются на диск, а не заполняют кучу PHP. - Проектная цель — O(1) кучи на страницу: удержание документа не становится дороже по мере добавления страниц. Именно вокруг этой инженерной цели выстроен писатель.
- «Бюджет производительности» — это реальная структурированная сущность в системе документации: предел по реальному времени и предел по пиковой памяти, выраженные как проверяемый контракт. Он формулирует обязательство. Это не результат теста производительности.
- Конкретные числа рассматриваются как живой сигнал: их измеряют по заявленной методике, а не фиксируют в тексте, где они могли бы незаметно устареть.
Как NextPDF подходит к этому
Заголовок раздела «Как NextPDF подходит к этому»Потоковый писатель следует простому правилу: не удерживать то, что можно выдать.
- Start page A single active cursor; no document-wide page graph in memory.
- Finalise page Page content + page object written straight to the output stream.
- Release buffer The finalised page buffer is dropped; the heap returns to baseline.
- Record offset to disk Xref and Kids entries go to php://temp/maxmemory:0 — immediate disk spill.
- Close Pages-tree root, Catalog, and trailer written once at the end.
Сброс на диск здесь ключевой. php://temp в PHP держит небольшой объём данных в памяти и сбрасывает их на диск только при превышении порога. Писатель открывает эти временные потоки с параметром maxmemory:0, который заставляет их сбрасываться на диск немедленно: порог хранения в памяти равен нулю. Практический эффект таков: служебные данные на каждый объект, которые по определению растут вместе с документом, никогда не накапливаются в куче. Они накапливаются на диске, где размер обычно не является ограничивающим фактором. Без этого параметра окно хранения в памяти по умолчанию сначала заполнилось бы и только потом сбросилось на диск, что свело бы на нет цель ограниченного потребления памяти именно тогда, когда это важнее всего.
Сам бюджет производительности — вторая половина истории. Это контракт системы документации, а не маркетинговое утверждение. Схема определяет бюджет как два целочисленных ограничения: предел по реальному времени в миллисекундах и предел по пиковому резидентному потреблению памяти в мебибайтах. Рецепт, объявляющий бюджет, объявляет проверяемое обязательство — так же как типизированная сигнатура объявляет обязательство, которое может проверить компилятор. Ценность бюджета в том, что он заявлен и обеспечен соблюдением, а не в том, что он мал.
Что говорят свидетельства
Заголовок раздела «Что говорят свидетельства»Эта страница относится к категории Evidence: Mixed evidence , и это сделано намеренно: свидетельства действительно бывают трёх видов.
- Механизм, подтверждённый кодом. Потоковый писатель в
src/Writer/Streaming/StreamingPdfWriter.phpдокументирует и реализует постраничный цикл «выдать, затем освободить» и открывает свои потоки xref и Kids сphp://temp/maxmemory:0, чтобы принудительно выполнить немедленный сброс на диск, так что “PHP memory stays bounded regardless of object count.” Потоковая архитектура с одним курсором и без удержания дерева в памяти — это также архитектурное решение, зафиксированное в ADR-001 (конвейер отрисовки удерживает не более чем O(depth) состояния, а не O(n) узлов). - Бюджет как принцип проектирования. Поле
performance_budget— реальная, необязательная часть схемы документации, определённая как{ wall_ms, peak_mb }с явными верхними границами. По сути это контракт, соблюдение которого можно обеспечить. - Тест производительности как живой сигнал. В ADR-001 прямо указано, что контролируемые показатели пиковой памяти и реального времени для больших документов — это эмпирический ориентир, который нужно собирать и фиксировать по заявленной методике, а не число, которое утверждается в тексте. Поэтому эта страница излагает механизм и контракт, а за конкретными цифрами отсылает туда, где их измеряют.
Формат делает эту цель обоснованной, а не просто желательной. Поскольку таблица перекрёстных ссылок — это индекс смещений на каждый объект согласно Spec: ISO 32000-2, §7.5.4 ISO 32000-2 §7.5.4 , генератор способен записывать объекты по мере их завершения и хранить только их смещения. Ограниченное потребление памяти согласуется с форматом файла, а не борется с ним.
Практический пример
Заголовок раздела «Практический пример»Ограниченное потребление памяти — это свойство способа генерации, а не флаг, который вы устанавливаете. Пакетный цикл, который завершает и освобождает каждый документ, удерживает кучу на ровном уровне на протяжении всего выполнения:
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Core\PdfFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// Process-lifetime, shared once.$factory = PdfFactory::new() ->withCompress(true) ->withDocumentFactory(new DocumentFactory( new FontRegistry(), new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024), ));
// Per-document, created and released each iteration.foreach ($invoiceBatch as $invoice) { $doc = $factory->create(); $doc->addPage(); $doc->writeHtml($invoice->toHtml()); $doc->save($invoice->outputPath()); unset($doc); // the document model is not carried into the next iteration}Реестры используются совместно, потому что однократный разбор шрифтов и изображений — в этом и состоит смысл фонового обработчика. Документ не используется совместно и освобождается на каждом проходе. Благодаря этому потребление памяти при пакетной обработке ограничено одним документом, а не всей партией.
Распространённое заблуждение
Заголовок раздела «Распространённое заблуждение»Самое распространённое заблуждение — воспринимать «ограниченное потребление памяти» как утверждение из теста производительности и ждать число в мегабайтах, которое можно процитировать. Это переворачивает смысл сказанного. Гарантия здесь структурная: писатель устроен так, чтобы удержание документа не обходилось дороже по мере добавления страниц. Конкретное пиковое значение зависит от содержимого страницы, шрифтов и изображений и имеет смысл только вместе с методикой измерения, поэтому ему место в тесте производительности, а не на этой странице.
Вторая ловушка — думать, что php://temp уже защищает вас. Защищает, но только после того, как заполнится его окно хранения в памяти по умолчанию. Именно параметр maxmemory:0 делает сброс на диск немедленным. Эта деталь и есть механизм. Без него это свойство не выполнялось бы как раз на тех больших документах, ради которых оно существует.
Ограничения и границы
Заголовок раздела «Ограничения и границы»Эта страница объясняет механизм потоковой записи и значение бюджета производительности. Она не приводит измеренные показатели пиковой памяти или пропускной способности. Их даёт тестирование производительности по заявленной методике, и ADR-001 явно оставляет эмпирические числа за этим измерением. Ограниченность «в расчёте на документ» не означает постоянство независимо от содержимого отдельного документа: страница со множеством крупных встроенных изображений по-прежнему требует столько памяти, сколько стоят эти изображения. Не растут служебные данные на каждую страницу и удерживаемый граф страниц. Не каждый путь генерации использует потоковый писатель. Какие пути выполняют потоковую запись, а какие буферизуют, определяется кодом и формой конвейера, а не этим обзором. Описанный механизм точен на дату проверки этой страницы. Авторитетные источники — src/Writer/Streaming/ и ADR-001 в основном репозитории.
Архитектура с потоковой записью и ограниченным потреблением памяти — это свойство Core. Редакции этого не меняют:
| Edition | Availability |
|---|---|
| Core | Core предоставляет архитектуру писателя с потоковой записью и сбросом на диск. |
| Pro | Pro наследует тот же писатель с ограниченным потреблением памяти; он добавляет возможности, а не другую модель памяти. |
| Enterprise | Enterprise наследует тот же писатель с ограниченным потреблением памяти; он добавляет возможности, а не другую модель памяти. |
Связанная документация
Заголовок раздела «Связанная документация»- Модель конвейера — где этап писателя находится в потоке обработки документа.
- Честное тестирование производительности — как NextPDF сообщает числа, которые эта страница намеренно не утверждает.
- Генерация документов большого объёма — пакетный сценарий, для которого создан этот механизм.
Глоссарий
Заголовок раздела «Глоссарий»- Ограниченное потребление памяти — проектное свойство, при котором удержание документа не требует больше кучи по мере добавления страниц (цель O(1) на страницу).
- Потоковый писатель — писатель, который выдаёт каждую страницу в вывод и освобождает её буфер, а не удерживает весь документ.
php://temp/maxmemory:0— временный поток PHP, принудительно сбрасываемый на диск немедленно, используемый для растущих служебных данных на каждый объект.- Бюджет производительности — структурированный контракт документации: предел по реальному времени и предел по пиковой памяти, заявленные и проверяемые.
- Живой сигнал — измеренное значение, сообщаемое вместе со своей методикой в заявленных условиях, а не фиксированное число, встроенное в текст.