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

Reply via email to