From 25bfecff6c6760786572cb7f3bf92bde623718d6 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Tue, 9 Jun 2026 00:44:36 +0100 Subject: [PATCH] perf(measurement): line-metrics cache stops adding when full, not clear-on-overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The process-wide line-metrics cache clear()-ed every entry once it passed 50,000 distinct styles — a non-atomic check-then-clear that is a thundering-herd recompute under concurrent rendering. Stop inserting at the cap and keep the existing entries instead; this runs only on a cache miss, never on the per-measurement get() path. Measured line metrics are unchanged. Distinct styles are few in real use, so this only guards a pathological style explosion. Finding 13 (cache). (The width-cache LRU, Finding 9, was dropped: it added a per-get reorder cost to the hot path for a rare-overflow benefit.) --- CHANGELOG.md | 9 +++++++++ .../FontLibraryTextMeasurementSystem.java | 12 +++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da2d5e26..cd505abb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,15 @@ Open cycle — bug-fix / housekeeping. Entries land here as they merge. large table this removes the dominant per-cell layout allocation. No public API or behaviour change. +- **Process-wide line-metrics cache stops inserting instead of flushing when full.** + The static line-metrics cache `clear()`-ed every entry once it passed 50,000 + distinct styles — a full flush whose non-atomic check-then-clear is a + thundering-herd recompute under concurrent rendering. It now stops inserting at + the cap and keeps the existing entries (distinct styles are few in real use, so + this is only a pathological-explosion guard; it runs on a cache miss, never on + the per-measurement path). **Measured line metrics are unchanged.** No public API + or behaviour change. + ### Tests / tooling - **Benchmark regression gate and measurement probe (benchmarks module, not part diff --git a/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java b/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java index b94e3f29..500e219d 100644 --- a/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java +++ b/src/main/java/com/demcha/compose/engine/measurement/FontLibraryTextMeasurementSystem.java @@ -100,10 +100,16 @@ private GlobalPdfStyleKey globalPdfStyleKey(PdfFont font, TextStyle style) { } private static void cacheGlobalLineMetrics(GlobalPdfStyleKey key, LineMetrics metrics) { - if (GLOBAL_PDF_LINE_METRICS_CACHE.size() > GLOBAL_LINE_METRICS_CACHE_LIMIT) { - GLOBAL_PDF_LINE_METRICS_CACHE.clear(); + // Safety cap on the process-wide line-metrics cache. Distinct styles are + // few in real use (a handful of font/size/decoration combos); this only + // guards a pathological style explosion. Stop inserting once full instead + // of clear()-ing: the old full flush wiped every hot entry under + // concurrent rendering (a thundering-herd recompute), so keeping the + // existing entries is strictly better. This runs on a cache miss only, + // never on the per-measurement get() path. + if (GLOBAL_PDF_LINE_METRICS_CACHE.size() < GLOBAL_LINE_METRICS_CACHE_LIMIT) { + GLOBAL_PDF_LINE_METRICS_CACHE.putIfAbsent(key, metrics); } - GLOBAL_PDF_LINE_METRICS_CACHE.putIfAbsent(key, metrics); } @Override