Skip to content
Merged
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,44 @@ follow semantic versioning; release dates are ISO 8601.

Open cycle — bug-fix / housekeeping. Entries land here as they merge.

### Performance

- **Text wrapping stops re-measuring the growing line prefix.** The greedy line
wrapper in `TextFlowSupport` now keeps a running line width and measures each
token once, instead of re-measuring the whole accumulated line on every token.
This removes O(line-length × tokens) measured-character work — and the
per-glyph sanitize/encode it triggered — from paragraph layout. **Output is
byte-identical: all layout and visual-regression snapshots pass unchanged.**
The effect is workload-dependent and concentrated in long-text documents;
measured locally (same-session A/B, full profile) a long multi-page proposal
rendered markedly faster, and a measurement-count probe showed ~9× fewer
measured characters on a long paragraph. No public API or behaviour change.

- **Long-token line breaking is no longer quadratic.** `TextFlowSupport.fitCharacters`
now binary-searches the break point instead of re-measuring every growing prefix
one character at a time. For an unbreakable run (long URL/ID, no-space CJK, or a
very narrow column) this cuts measurement calls and measured characters by
~80–85% (probe: 652 → 97 width calls, 36k → 7k measured chars on a 600-char
token). **Output is byte-identical** — the fit predicate is monotonic, so the
search returns the same break index. No public API or behaviour change.

### Tests / tooling

- **Benchmark regression gate and measurement probe (benchmarks module, not part
of the published library).** `BenchmarkVerdictTool` compares a current-speed run
to the committed baseline (`baselines/current-speed-full.json`) and reports
improved / neutral / regressed. The hard gate fails only on an **average-latency**
regression beyond the noise band; peak heap is **advisory** (the `peakHeapMb`
used-heap delta is GC-timing noisy — use the probe's per-compile allocation
bytes for deterministic heap). A single run is advisory; the hard gate needs a
median (`-Repeat` >= 2).
`MeasurementCountBenchmark` + `CountingTextMeasurementSystem` capture
deterministic measurement-call counts and per-compile allocation bytes for
proving algorithmic / allocation changes (the probe warms up the JVM before its
allocation window, so `Alloc KB` reflects steady state, not one-time
class-load / JIT cold-start). `scripts/run-benchmarks.ps1` gains the
`11-verdict-current-speed` step (skippable via `-SkipVerdict`).

## v1.7.0 — 2026-06-07

Canonical DSL primitives — additive only, zero breaking changes. Adding public
Expand Down
88 changes: 88 additions & 0 deletions baselines/current-speed-full.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"timestamp" : "2026-06-08 12:07:23",
"profile" : "full",
"warmupIterations" : 12,
"measurementIterations" : 40,
"docsPerThread" : 12,
"threadCounts" : [ 1, 2, 4, 8 ],
"latency" : [ {
"scenario" : "cv-template",
"description" : "Compose-first CV template",
"avgMillis" : 4.28,
"p50Millis" : 3.93,
"p95Millis" : 5.83,
"maxMillis" : 7.15,
"docsPerSecond" : 233.52,
"avgKilobytes" : 2.29,
"peakHeapMb" : 33.08
}, {
"scenario" : "engine-simple",
"description" : "One-page engine composition",
"avgMillis" : 3.17,
"p50Millis" : 2.96,
"p95Millis" : 5.01,
"maxMillis" : 5.9,
"docsPerSecond" : 315.87,
"avgKilobytes" : 1.08,
"peakHeapMb" : 12.0
}, {
"scenario" : "feature-rich",
"description" : "QR, barcode, watermark, header/footer, page break",
"avgMillis" : 45.37,
"p50Millis" : 37.09,
"p95Millis" : 60.65,
"maxMillis" : 69.62,
"docsPerSecond" : 22.04,
"avgKilobytes" : 6.37,
"peakHeapMb" : 86.14
}, {
"scenario" : "invoice-template",
"description" : "Compose-first invoice template",
"avgMillis" : 19.42,
"p50Millis" : 18.75,
"p95Millis" : 27.88,
"maxMillis" : 34.26,
"docsPerSecond" : 51.5,
"avgKilobytes" : 9.72,
"peakHeapMb" : 85.09
}, {
"scenario" : "proposal-template",
"description" : "Long multi-page proposal template",
"avgMillis" : 14.41,
"p50Millis" : 13.71,
"p95Millis" : 19.18,
"maxMillis" : 19.93,
"docsPerSecond" : 69.38,
"avgKilobytes" : 7.72,
"peakHeapMb" : 97.52
} ],
"throughput" : [ {
"scenario" : "invoice-template",
"threads" : 1,
"totalDocs" : 12,
"docsPerSecond" : 81.22,
"avgMillisPerDoc" : 12.31
}, {
"scenario" : "invoice-template",
"threads" : 2,
"totalDocs" : 24,
"docsPerSecond" : 158.68,
"avgMillisPerDoc" : 6.3
}, {
"scenario" : "invoice-template",
"threads" : 4,
"totalDocs" : 48,
"docsPerSecond" : 265.11,
"avgMillisPerDoc" : 3.77
}, {
"scenario" : "invoice-template",
"threads" : 8,
"totalDocs" : 96,
"docsPerSecond" : 356.61,
"avgMillisPerDoc" : 2.8
} ],
"totalBytes" : 2905520,
"aggregation" : "median",
"sourceCount" : 7,
"sourceRuns" : [ "C:\\Users\\Demch\\OneDrive\\Java\\GraphCompose\\target\\benchmarks\\current-speed\\run-20260608-120624.json", "C:\\Users\\Demch\\OneDrive\\Java\\GraphCompose\\target\\benchmarks\\current-speed\\run-20260608-120635.json", "C:\\Users\\Demch\\OneDrive\\Java\\GraphCompose\\target\\benchmarks\\current-speed\\run-20260608-120645.json", "C:\\Users\\Demch\\OneDrive\\Java\\GraphCompose\\target\\benchmarks\\current-speed\\run-20260608-120655.json", "C:\\Users\\Demch\\OneDrive\\Java\\GraphCompose\\target\\benchmarks\\current-speed\\run-20260608-120704.json", "C:\\Users\\Demch\\OneDrive\\Java\\GraphCompose\\target\\benchmarks\\current-speed\\run-20260608-120713.json", "C:\\Users\\Demch\\OneDrive\\Java\\GraphCompose\\target\\benchmarks\\current-speed\\run-20260608-120722.json" ]
}
Loading