Performance: memory-fragmentation analyzer
At a glance
Section titled “At a glance”The Performance module has a narrow scope. It provides one observation-only
tool, MemoryFragmentationAnalyzer, that measures peak and retained memory
across marked windows of engine work. Its public surface also includes the
immutable snapshot the tool produces. It does not enforce budgets, throttle
work, or change engine behavior.
Scope and stability. The real surface of this module is two classes (
MemoryFragmentationAnalyzer,MemoryFragmentationSnapshot). It is not a per-operation budget enforcement harness. Theperformance_budgetvalue in every module’s frontmatter is a documentation convention, not a value this module enforces. The surface isexperimental: it is a diagnostic introduced in@since 3.2.0. Its snapshot shape may evolve.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”Resource utilization is a first-class quality concern for a Portable Document Format (PDF) engine. The minimal observable is the separation between peak memory (the high-water mark during a window) and retained memory (what is still held after the window). This module measures only that.
MemoryFragmentationAnalyzer only observes; it does not mutate writer or
document state. reset() runs a garbage collection (GC) cycle and resets
PHP’s peak counter so subsequent measurements belong to the window since the
reset. mark(string $label) captures a MemoryFragmentationSnapshot at a
labeled point. snapshots() returns the captured series. peakDelta() and
retainedDelta() report the peak and retained change across the run.
MemoryFragmentationSnapshot is a final readonly value object: a labeled
point with transientBytes() (peak minus retained, which is memory that was
used and released), retentionRatio() (retained over peak), and toArray()
for export. A high transient-bytes value with a low retention ratio indicates
churn that a buffer-reuse strategy could remove. Both classes are
@since 3.2.0.
API surface
Section titled “API surface”| Class | Key members | Role |
|---|---|---|
MemoryFragmentationAnalyzer | reset(), mark(string $label), snapshots(), peakDelta(), retainedDelta() | Observation-only memory analyzer (@since 3.2.0) |
MemoryFragmentationSnapshot | transientBytes(), retentionRatio(), toArray() | Immutable labeled measurement (@since 3.2.0) |
Run composer docs:generate-api-php -- --module=Performance to generate the
full PHPDoc table.
Code sample — Quick start
Section titled “Code sample — Quick start”Instrument a hot path, then read the deltas.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Performance\MemoryFragmentationAnalyzer;
$analyzer = new MemoryFragmentationAnalyzer();$analyzer->reset();
$analyzer->mark('before-write');// ... engine work under observation ...$analyzer->mark('after-write');
printf("Peak delta: %d B, retained delta: %d B\n", $analyzer->peakDelta(), $analyzer->retainedDelta());Code sample — Production
Section titled “Code sample — Production”Wrap a render and emit the fragmentation snapshot to a metrics sink. Treat a low retention ratio under high transient bytes as a churn signal.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Performance\MemoryFragmentationAnalyzer;use Psr\Log\LoggerInterface;
final readonly class RenderMemoryProbe{ public function __construct(private LoggerInterface $logger) {}
/** @param callable():void $render The render closure to observe. */ public function observe(callable $render): void { $analyzer = new MemoryFragmentationAnalyzer(); $analyzer->reset();
$analyzer->mark('start'); $render(); $analyzer->mark('end');
foreach ($analyzer->snapshots() as $snapshot) { $this->logger->info('mem-frag', $snapshot->toArray()); } }}Edge cases & gotchas
Section titled “Edge cases & gotchas”reset()callsgc_collect_cycles()andmemory_reset_peak_usage(). It has a process-global effect on PHP’s peak counter. Do not interleave it with another component that reads the same counter in the same request.- Measurements belong to the window since the last
reset(). Amark()without a precedingreset()measures from process start, which is usually not what you want. - This is a diagnostic, not a control. It never throttles or aborts work. Do not build back pressure on it.
- The reproducibility profile is
structural: byte figures depend on the runtime, allocator, and GC state. Two runs can differ numerically even for the same logical work.
Performance
Section titled “Performance”The analyzer’s own overhead is a garbage collection cycle on reset() and one
hrtime() / memory_get_* read per mark(), which is negligible relative to
the work it observes. It allocates one small snapshot per mark(). The
performance_budget value in this frontmatter is the documentation-wide
reference figure; this module does not enforce it.
Security notes
Section titled “Security notes”Memory figures are diagnostic data. They do not contain document content, but a
fine-grained memory profile can reveal input size and shape. Treat snapshot
exports as internal telemetry, and apply the project’s log-scrubbing obligation
before you share them externally. The module performs no input/output (I/O) and
embeds no external data. See the engine threat model in
/modules/core/security/.
Conformance
Section titled “Conformance”This module makes no normative claim about the PDF specification. It is a
memory diagnostic and implements no standardized protocol with clauses to cite.
Its architectural rationale references the resource-utilization quality view
from the ISO/IEC/IEEE 42010 architecture-description framework. That reference
is an architecture-practice alignment, not a PDF citation. Engine conformance
is validated by the oracle and golden suites described in
/modules/core/conformance/.
See also
Section titled “See also”- Observability module — the broader runtime-state surface.
- Telemetry module — emits metrics to an external backend.
- Writer module — a common subject of memory observation.
- Engine security model