Integrated: 8356814: LineBreakMeasurer.nextLayout() slower than necessary when no break needed
Daniel Gredler
dgredler at openjdk.org
Thu May 22 21:27:55 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 + ")");
> }
>
> }
This pull request has now been integrated.
Changeset: fdda7661
Author: Daniel Gredler <dgredler at openjdk.org>
Committer: Harshitha Onkar <honkar at openjdk.org>
URL: https://git.openjdk.org/jdk/commit/fdda7661906eab63d939e9f482449e21cc143c8f
Stats: 8 lines in 1 file changed: 7 ins; 0 del; 1 mod
8356814: LineBreakMeasurer.nextLayout() slower than necessary when no break needed
Reviewed-by: prr, dnguyen, honkar
-------------
PR: https://git.openjdk.org/jdk/pull/25193
More information about the client-libs-dev
mailing list