RFR: 8356814: LineBreakMeasurer.nextLayout() slower than necessary when no break needed
Harshitha Onkar
honkar at openjdk.org
Thu May 22 21:22:51 UTC 2025
On Mon, 12 May 2025 20:35:52 GMT, Daniel Gredler <dgredler at openjdk.org> wrote:
> `LineBreakMeasurer.nextLayout()` calls `nextOffset()` internally to calculate the layout limit. When this happens, a `GlyphVector` is created and the layout engine is invoked to shape the text. The `GlyphVector` is cached in the relevant `ExtendedTextSourceLabel` component.
>
> `LineBreakMeasurer.nextLayout()` then calls `TextMeasurer.getLayout()` which eventually asks that same `ExtendedTextSourceLabel` component for a subset component. This triggers the creation of a fresh `ExtendedTextSourceLabel` without the cached `GlyphVector`.
>
> However, this fresh `ExtendedTextSourceLabel` is not necessary if the subset requested perfectly matches the already-existing `ExtendedTextSourceLabel` component. This happens when the text is short enough that no line break is needed.
>
> I think we should change `ExtendedTextSourceLabel.getSubset()` to return `this` if the requested subset is identical to the existing instance. This will allow us to use the existing cached `GlyphVector`, and the call to `LineBreakMeasurer.nextLayout()` will trigger text shaping once, rather than twice.
>
> In local testing, the test program below ran in ~1250 ms before this optimization, and ran in ~960 ms after the change (a 23% reduction in run time).
>
> The following three existing test classes provide good regression test coverage for this change:
> - test/jdk/java/awt/font/LineBreakMeasurer/LineBreakWithTrackingAuto
> - test/jdk/java/awt/font/LineBreakMeasurer/TestLineBreakWithFontSub
> - test/jdk/java/awt/font/LineBreakMeasurer/FRCTest
>
>
> public class LineBreakMeasurerPerfTest {
>
> public static void main(String[] args) {
>
> float advance = 0;
> long start = System.currentTimeMillis();
> AttributedString string = new AttributedString("This is a test.");
> FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true);
>
> for (int i = 0; i < 100_000; i++) {
> LineBreakMeasurer measurer = new LineBreakMeasurer(string.getIterator(), frc);
> TextLayout layout = measurer.nextLayout(999); // large enough to not require break
> advance = Math.max(advance, layout.getAdvance());
> }
>
> long end = System.currentTimeMillis();
> System.out.println((end - start) + " ms elapsed (advance: " + advance + ")");
> }
>
> }
LGTM. Ran LineBreakMeasurer tests, in particular the manual test LineBreakWithTracking.java to see if there was an effect of tracking + manual window resize on the line break lengths. There were multiple instances when the same object was returned so the fix looks beneficial in those case. Did not observe any regressions.
With the POC, noticed ~1000ms difference without and with fix on some runs.
-------------
Marked as reviewed by honkar (Reviewer).
PR Review: https://git.openjdk.org/jdk/pull/25193#pullrequestreview-2862595417
More information about the client-libs-dev
mailing list