While profiling some code which uses `LineBreakMeasurer`, I noticed that a
non-trivial amount of time was being spent in `AttributedString`, because
iterating through an `AttributedString` and getting a specific attribute for
each character ends up calling `Vector.indexOf` inside of
`AttributedString.getAttribute`. This boils down to the data structure choice
of storing run attribute data in two arrays of `Vector`s, rather than in a
single array of `Map`s. Based on testing, it looks like a single array of
`Map`s would be simpler, faster, and require slightly less memory in some cases.
A JMH benchmark is included in this PR. With the updated code I'm seeing a 35%
to 40% reduction in runtime for the iteration benchmark tests with one or more
attributes. I checked memory usage by enabling GC profiling and looking at the
three creation benchmark tests; it seems like there is no difference when the
string has no attributes, a 20% reduction in memory use when the string has a
single attribute, and a very small 1% reduction in memory use when we set four
attributes. Full JMH results below (I focused on the `us/op` and `B/op` results
specifically).
Tests run:
- make test TEST="jtreg:test/jdk/java/text"
- make test TEST="jtreg:test/jdk/java/awt/font"
- make test TEST="jtreg:test/jdk/java/awt/Graphics2D/DrawString"
- make test TEST="micro:java.text.AttributedStringBench" MICRO="OPTIONS=-prof
gc"
I've assumed that the existing tests provide adequate regression test coverage,
but there may be other Oracle-internal tests that should also be run to verify
correctness.
JMH results **before** PR:
Benchmark Mode Cnt
Score Error Units
AttributedStringBench.creationWithFourAttributes avgt 15
0.182 ± 0.004 us/op
AttributedStringBench.creationWithFourAttributes:gc.alloc.rate avgt 15
3989.259 ± 85.345 MB/sec
AttributedStringBench.creationWithFourAttributes:gc.alloc.rate.norm avgt 15
760.001 ± 0.001 B/op
AttributedStringBench.creationWithFourAttributes:gc.count avgt 15
151.000 counts
AttributedStringBench.creationWithFourAttributes:gc.time avgt 15
116.000 ms
AttributedStringBench.creationWithNoAttributes avgt 15
0.003 ± 0.001 us/op
AttributedStringBench.creationWithNoAttributes:gc.alloc.rate avgt 15
10506.090 ± 336.923 MB/sec
AttributedStringBench.creationWithNoAttributes:gc.alloc.rate.norm avgt 15
32.000 ± 0.001 B/op
AttributedStringBench.creationWithNoAttributes:gc.count avgt 15
201.000 counts
AttributedStringBench.creationWithNoAttributes:gc.time avgt 15
192.000 ms
AttributedStringBench.creationWithOneAttribute avgt 15
0.045 ± 0.001 us/op
AttributedStringBench.creationWithOneAttribute:gc.alloc.rate avgt 15
7951.526 ± 85.521 MB/sec
AttributedStringBench.creationWithOneAttribute:gc.alloc.rate.norm avgt 15
376.000 ± 0.001 B/op
AttributedStringBench.creationWithOneAttribute:gc.count avgt 15
152.000 counts
AttributedStringBench.creationWithOneAttribute:gc.time avgt 15
137.000 ms
AttributedStringBench.iterationWithFourAttributes avgt 15
0.363 ± 0.003 us/op
AttributedStringBench.iterationWithFourAttributes:gc.alloc.rate avgt 15
126.041 ± 1.198 MB/sec
AttributedStringBench.iterationWithFourAttributes:gc.alloc.rate.norm avgt 15
48.003 ± 0.001 B/op
AttributedStringBench.iterationWithFourAttributes:gc.count avgt 15
60.000 counts
AttributedStringBench.iterationWithFourAttributes:gc.time avgt 15
45.000 ms
AttributedStringBench.iterationWithNoAttributes avgt 15
0.064 ± 0.001 us/op
AttributedStringBench.iterationWithNoAttributes:gc.alloc.rate avgt 15
715.475 ± 12.965 MB/sec
AttributedStringBench.iterationWithNoAttributes:gc.alloc.rate.norm avgt 15
48.000 ± 0.001 B/op
AttributedStringBench.iterationWithNoAttributes:gc.count avgt 15
113.000 counts
AttributedStringBench.iterationWithNoAttributes:gc.time avgt 15
73.000 ms
AttributedStringBench.iterationWithOneAttribute avgt 15
0.329 ± 0.002 us/op
AttributedStringBench.iterationWithOneAttribute:gc.alloc.rate avgt 15
139.258 ± 0.957 MB/sec
AttributedStringBench.iterationWithOneAttribute:gc.alloc.rate.norm avgt 15
48.002 ± 0.001 B/op
AttributedStringBench.iterationWithOneAttribute:gc.count avgt 15
66.000 counts
AttributedStringBench.iterationWithOneAttribute:gc.time avgt 15
54.000 ms
JMH results **after** PR:
Benchmark Mode Cnt
Score Error Units
AttributedStringBench.creationWithFourAttributes avgt 15
0.148 ± 0.002 us/op
AttributedStringBench.creationWithFourAttributes:gc.alloc.rate avgt 15
4851.679 ± 57.903 MB/sec
AttributedStringBench.creationWithFourAttributes:gc.alloc.rate.norm avgt 15
752.001 ± 0.001 B/op
AttributedStringBench.creationWithFourAttributes:gc.count avgt 15
104.000 counts
AttributedStringBench.creationWithFourAttributes:gc.time avgt 15
92.000 ms
AttributedStringBench.creationWithNoAttributes avgt 15
0.003 ± 0.001 us/op
AttributedStringBench.creationWithNoAttributes:gc.alloc.rate avgt 15
10805.692 ± 530.472 MB/sec
AttributedStringBench.creationWithNoAttributes:gc.alloc.rate.norm avgt 15
32.000 ± 0.001 B/op
AttributedStringBench.creationWithNoAttributes:gc.count avgt 15
208.000 counts
AttributedStringBench.creationWithNoAttributes:gc.time avgt 15
186.000 ms
AttributedStringBench.creationWithOneAttribute avgt 15
0.032 ± 0.001 us/op
AttributedStringBench.creationWithOneAttribute:gc.alloc.rate avgt 15
8936.917 ± 136.734 MB/sec
AttributedStringBench.creationWithOneAttribute:gc.alloc.rate.norm avgt 15
304.000 ± 0.001 B/op
AttributedStringBench.creationWithOneAttribute:gc.count avgt 15
172.000 counts
AttributedStringBench.creationWithOneAttribute:gc.time avgt 15
162.000 ms
AttributedStringBench.iterationWithFourAttributes avgt 15
0.239 ± 0.037 us/op
AttributedStringBench.iterationWithFourAttributes:gc.alloc.rate avgt 15
194.596 ± 27.090 MB/sec
AttributedStringBench.iterationWithFourAttributes:gc.alloc.rate.norm avgt 15
48.002 ± 0.001 B/op
AttributedStringBench.iterationWithFourAttributes:gc.count avgt 15
91.000 counts
AttributedStringBench.iterationWithFourAttributes:gc.time avgt 15
57.000 ms
AttributedStringBench.iterationWithNoAttributes avgt 15
0.063 ± 0.001 us/op
AttributedStringBench.iterationWithNoAttributes:gc.alloc.rate avgt 15
724.316 ± 10.086 MB/sec
AttributedStringBench.iterationWithNoAttributes:gc.alloc.rate.norm avgt 15
48.000 ± 0.001 B/op
AttributedStringBench.iterationWithNoAttributes:gc.count avgt 15
114.000 counts
AttributedStringBench.iterationWithNoAttributes:gc.time avgt 15
75.000 ms
AttributedStringBench.iterationWithOneAttribute avgt 15
0.204 ± 0.003 us/op
AttributedStringBench.iterationWithOneAttribute:gc.alloc.rate avgt 15
224.211 ± 2.974 MB/sec
AttributedStringBench.iterationWithOneAttribute:gc.alloc.rate.norm avgt 15
48.001 ± 0.001 B/op
AttributedStringBench.iterationWithOneAttribute:gc.count avgt 15
105.000 counts
AttributedStringBench.iterationWithOneAttribute:gc.time avgt 15
72.000 ms
-------------
Commit messages:
- Use maps to store run attributes in AttributedString
Changes: https://git.openjdk.org/jdk/pull/30402/files
Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=30402&range=00
Issue: https://bugs.openjdk.org/browse/JDK-8380794
Stats: 225 lines in 2 files changed: 122 ins; 70 del; 33 mod
Patch: https://git.openjdk.org/jdk/pull/30402.diff
Fetch: git fetch https://git.openjdk.org/jdk.git pull/30402/head:pull/30402
PR: https://git.openjdk.org/jdk/pull/30402