-
Bug
-
Resolution: Unresolved
-
P4
-
24
-
In Review
`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).
------
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 + ")");
}
}
`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).
------
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 + ")");
}
}
- links to
-
Review(master) openjdk/jdk/25193