Skip to content

Conversation

@wenshao
Copy link
Contributor

@wenshao wenshao commented Nov 22, 2025

This PR optimizes the performance of java.time.format.DateTimeFormatter by unrolling loops in the formatting and parsing operations.

When we run the format and parse of java.time.DateTimeFormatter using -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining, we can see the following output:

@ 40   j.t.f.DTFB$CompositePrinterParser::format (116 bytes)   inline (hot)
  @ 1   f.l.StringBuilder::length (5 bytes)   inline (hot)
    @ 1   j.l.AbstractStringBuilder::length (5 bytes)   accessor
  @ 48   f.t.f.DTFB$DateTimePrinterParser::format (0 bytes)   failed to inline: virtual call

@ 37   j.t.f.DTFB$CompositePrinterParser::parse (135 bytes)   inline (hot)
   @ 114   j.t.f.DTFB$DateTimePrinterParser::parse (0 bytes)   failed to inline: virtual call

As seen in this log, both the DateTimeFormatterBuilder$CompositePrinterParser::format and DateTimeFormatterBuilder$CompositePrinterParser::parse methods are failed to inline: virtual call. We can eliminate this inline failure by manually unrolling the loop.

Once manually unrolled, inline optimizations can work, enabling optimizations like TypeProfile to take effect and thus improve performance.

Below is the log output after manually unrolling the loop:

@ 41   j.t.f.DTFB$CompositePrinterParser::format (40 bytes)   inline (hot)
  @ 1   j.l.StringBuilder::length (5 bytes)   inline (hot)
    @ 1   j.l.AbstractStringBuilder::length (5 bytes)   accessor
  @ 22   j.t.f.DateTimePrinterParserFactory$$Lambda/0x00000ff801009df8::format (11 bytes)   inline (hot)   
         callee changed to  j.t.f.DTFB$CompositePrinterParser::format (40 bytes)
             \-> TypeProfile (6212/6212 counts) = j/t/f/DateTimePrinterParserFactory$$Lambda+0x00000ff801009df8
    @ 7   j.t.f.DateTimePrinterParserFactory::lambda$createFormatter$11 (195 bytes)   inline (hot)
      @ 6   j.t.f.DTFB$NumberPrinterParser::format (399 bytes)   failed to inline: hot method too big   
           callee changed to  j.t.f.DateTimePrinterParserFactory::lambda$createFormatter$11 (195 bytes)    
           	      \-> TypeProfile (7170/7170 counts) = j/t/f/DTFB$NumberPrinterParser
        @ 20   j.t.f.DTFB$CharLiteralPrinterParser::format (11 bytes)   inline (hot)   
        callee changed to  j.t.f.DateTimePrinterParserFactory::lambda$createFormatter$11 (195 bytes) 
           \-> TypeProfile (7170/7170 counts) = j/t/f/DateTimeFormatterBuilder$CharLiteralPrinterParser
 @ 37   j.t.f.DTFB$CompositePrinterParser::parse (13 bytes)   inline (hot)
   @ 7   j.t.f.DateTimePrinterParserFactory$$Lambda/0x000000800100a950::parse (11 bytes)   inline (hot)
      callee changed to  j.t.f.DTFB$CompositePrinterParser::parse (13 bytes) 
         \-> TypeProfile (130649/130649 counts) = j/t/f/DateTimePrinterParserFactory$$Lambda+0x000000800100a950
     @ 7   j.t.f.DateTimePrinterParserFactory::lambda$createParser$9 (217 bytes)   inline (hot)
       @ 6   j.t.f.DTFB$NumberPrinterParser::parse (609 bytes)   failed to inline: hot method too big
          callee changed to  j.t.f.DateTimePrinterParserFactory::lambda$createParser$9 (217 bytes)
              \-> TypeProfile (130884/130884 counts) = j/t/f/DTFB$NumberPrinterParser
       @ 26   j.t.f.DTFB$CharLiteralPrinterParser::parse (91 bytes)   inline (hot)
          callee changed to  j.t.f.DateTimePrinterParserFactory::lambda$createParser$9 (217 bytes) 
          \-> TypeProfile (130884/130884 counts) = j/t/f/DTFB$CharLiteralPrinterParser

We see that the format and parse methods of both NumberPrinterParser and CharLiteralPrinterParser trigger TypeProfile optimization.

We can choose to generate the code for the unrolling loop based on MethodHandle, the ClassFile API, or Gensrc.gmk. Using MethodHandle or the ClassFile API will make the code obscure and difficult to understand. I recommend using Gensrc.gmk. One advantage of Gensrc.gmk is that the initial performance is better than other implementations.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/28465/head:pull/28465
$ git checkout pull/28465

Update a local copy of the PR:
$ git checkout pull/28465
$ git pull https://git.openjdk.org/jdk.git pull/28465/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 28465

View PR using the GUI difftool:
$ git pr show -t 28465

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/28465.diff

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 22, 2025

👋 Welcome back swen! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Nov 22, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added build build-dev@openjdk.org core-libs core-libs-dev@openjdk.org i18n i18n-dev@openjdk.org labels Nov 22, 2025
@openjdk
Copy link

openjdk bot commented Nov 22, 2025

@wenshao The following labels will be automatically applied to this pull request:

  • build
  • core-libs
  • i18n

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@wenshao wenshao force-pushed the datetime_unroll_2511 branch 2 times, most recently from d97e61e to 7088d79 Compare November 22, 2025 09:11
@wenshao wenshao changed the title Datetime unroll 2511 Improve DateTimeFormatter performance by unrolling formatting/parsing loops Nov 22, 2025
@wenshao
Copy link
Contributor Author

wenshao commented Nov 22, 2025

Below are the performance results on a MacBook M1 Pro.

1. Shell

# master
git checkout 9db0a6d473654a37159c43bfd56210d9b5330b15
make test TEST="micro:java.time.format.DateTimeFormatter"

# this pr
git checkout d82d9405772e72d091a362db013159396e6a9cd4
make test TEST="micro:java.time.format.DateTimeFormatter"

2. Raw Benchmark Data

-# 9db0a6d473654a37159c43bfd56210d9b5330b15 (master)
-Benchmark                                                                      (pattern)   Mode  Cnt      Score      Error   Units
-DateTimeFormatterBench.formatInstants                                           HH:mm:ss  thrpt   15     15.486 ±    0.385  ops/ms
-DateTimeFormatterBench.formatInstants                                       HH:mm:ss.SSS  thrpt   15     11.392 ±    0.271  ops/ms
-DateTimeFormatterBench.formatInstants                              yyyy-MM-dd'T'HH:mm:ss  thrpt   15      8.985 ±    0.425  ops/ms
-DateTimeFormatterBench.formatInstants                          yyyy-MM-dd'T'HH:mm:ss.SSS  thrpt   15      7.122 ±    0.646  ops/ms
-DateTimeFormatterBench.formatZonedDateTime                                      HH:mm:ss  thrpt   15     23.252 ±    0.073  ops/ms
-DateTimeFormatterBench.formatZonedDateTime                                  HH:mm:ss.SSS  thrpt   15     15.816 ±    0.110  ops/ms
-DateTimeFormatterBench.formatZonedDateTime                         yyyy-MM-dd'T'HH:mm:ss  thrpt   15     13.137 ±    0.332  ops/ms
-DateTimeFormatterBench.formatZonedDateTime                     yyyy-MM-dd'T'HH:mm:ss.SSS  thrpt   15      9.880 ±    0.050  ops/ms
-DateTimeFormatterParse.parseInstant                                                  N/A  thrpt   15   2104.714 ±  115.593  ops/ms
-DateTimeFormatterParse.parseLocalDate                                                N/A  thrpt   15   5032.899 ±  299.974  ops/ms
-DateTimeFormatterParse.parseLocalDateTime                                            N/A  thrpt   15   3600.533 ±  231.355  ops/ms
-DateTimeFormatterParse.parseLocalDateTimeWithNano                                    N/A  thrpt   15   3468.901 ±  287.957  ops/ms
-DateTimeFormatterParse.parseLocalTime                                                N/A  thrpt   15   4435.315 ±  365.496  ops/ms
-DateTimeFormatterParse.parseLocalTimeWithNano                                        N/A  thrpt   15   4580.805 ±  440.078  ops/ms
-DateTimeFormatterParse.parseOffsetDateTime                                           N/A  thrpt   15   2226.291 ±  206.005  ops/ms
-DateTimeFormatterParse.parseZonedDateTime                                            N/A  thrpt   15   1851.087 ±  118.679  ops/ms
-DateTimeFormatterWithPaddingBench.formatWithPadding                                  N/A  thrpt   15   7467.859 ± 1289.107  ops/ms
-DateTimeFormatterWithPaddingBench.formatWithPaddingLengthOne                         N/A  thrpt   15  11551.849 ± 1963.616  ops/ms
-DateTimeFormatterWithPaddingBench.formatWithPaddingLengthZero                        N/A  thrpt   15  14187.603 ± 1433.589  ops/ms


+# d82d9405772e72d091a362db013159396e6a9cd4 (this pr)
+Benchmark                                                                      (pattern)   Mode  Cnt      Score      Error   Units
+DateTimeFormatterBench.formatInstants                                           HH:mm:ss  thrpt   15     15.581 ±    0.253  ops/ms
+DateTimeFormatterBench.formatInstants                                       HH:mm:ss.SSS  thrpt   15     12.467 ±    0.876  ops/ms
+DateTimeFormatterBench.formatInstants                              yyyy-MM-dd'T'HH:mm:ss  thrpt   15      9.518 ±    0.233  ops/ms
+DateTimeFormatterBench.formatInstants                          yyyy-MM-dd'T'HH:mm:ss.SSS  thrpt   15      8.466 ±    0.490  ops/ms
+DateTimeFormatterBench.formatZonedDateTime                                      HH:mm:ss  thrpt   15     24.074 ±    0.165  ops/ms
+DateTimeFormatterBench.formatZonedDateTime                                  HH:mm:ss.SSS  thrpt   15     18.582 ±    0.097  ops/ms
+DateTimeFormatterBench.formatZonedDateTime                         yyyy-MM-dd'T'HH:mm:ss  thrpt   15     14.048 ±    0.083  ops/ms
+DateTimeFormatterBench.formatZonedDateTime                     yyyy-MM-dd'T'HH:mm:ss.SSS  thrpt   15     11.959 ±    0.077  ops/ms
+DateTimeFormatterParse.parseInstant                                                  N/A  thrpt   15   2117.107 ±  128.321  ops/ms
+DateTimeFormatterParse.parseLocalDate                                                N/A  thrpt   15   4974.686 ±  407.681  ops/ms
+DateTimeFormatterParse.parseLocalDateTime                                            N/A  thrpt   15   3702.507 ±  314.033  ops/ms
+DateTimeFormatterParse.parseLocalDateTimeWithNano                                    N/A  thrpt   15   3841.321 ±  286.259  ops/ms
+DateTimeFormatterParse.parseLocalTime                                                N/A  thrpt   15   4471.911 ±  546.928  ops/ms
+DateTimeFormatterParse.parseLocalTimeWithNano                                        N/A  thrpt   15   4604.469 ±  359.004  ops/ms
+DateTimeFormatterParse.parseOffsetDateTime                                           N/A  thrpt   15   2617.276 ±  366.495  ops/ms
+DateTimeFormatterParse.parseZonedDateTime                                            N/A  thrpt   15   1853.496 ±  123.845  ops/ms
+DateTimeFormatterWithPaddingBench.formatWithPadding                                  N/A  thrpt   15   8543.391 ± 1235.325  ops/ms
+DateTimeFormatterWithPaddingBench.formatWithPaddingLengthOne                         N/A  thrpt   15  12248.395 ± 2299.613  ops/ms
+DateTimeFormatterWithPaddingBench.formatWithPaddingLengthZero                        N/A  thrpt   15  25737.255 ± 1313.735  ops/ms

3. Performance Comparison

Comparison between commit 9db0a6d and d82d940

Benchmark Pattern 9db0a6d (ops/ms) d82d940 (ops/ms) Diff % Change Status
formatInstants HH:mm:ss 15.486 15.581 +0.095 +0.6%
formatInstants HH:mm:ss.SSS 11.392 12.467 +1.075 +9.5%
formatInstants yyyy-MM-dd'T'HH:mm:ss 8.985 9.518 +0.533 +6.0%
formatInstants yyyy-MM-dd'T'HH:mm:ss.SSS 7.122 8.466 +1.344 +18.9%
formatZonedDateTime HH:mm:ss 23.252 24.074 +0.822 +3.5%
formatZonedDateTime HH:mm:ss.SSS 15.816 18.582 +2.766 +17.5%
formatZonedDateTime yyyy-MM-dd'T'HH:mm:ss 13.137 14.048 +0.911 +6.9%
formatZonedDateTime yyyy-MM-dd'T'HH:mm:ss.SSS 9.880 11.959 +2.079 +21.0%
Benchmark 9db0a6d (ops/ms) d82d940 (ops/ms) Diff % Change Status
parseInstant 2104.714 2117.107 +12.393 +0.6%
parseLocalDate 5032.899 4974.686 -58.213 -1.2%
parseLocalDateTime 3600.533 3702.507 +101.974 +2.8%
parseLocalDateTimeWithNano 3468.901 3841.321 +372.420 +10.7%
parseLocalTime 4435.315 4471.911 +36.596 +0.8%
parseLocalTimeWithNano 4580.805 4604.469 +23.664 +0.5%
parseOffsetDateTime 2226.291 2617.276 +390.985 +17.6%
parseZonedDateTime 1851.087 1853.496 +2.409 +0.1%
formatWithPadding 7467.859 8543.391 +1075.532 +14.4%
formatWithPaddingLengthOne 11551.849 12248.395 +696.546 +6.0%
formatWithPaddingLengthZero 14187.603 25737.255 +11549.652 +81.4%

@wenshao wenshao marked this pull request as ready for review November 22, 2025 11:51
@wenshao wenshao force-pushed the datetime_unroll_2511 branch from 7088d79 to 76bc921 Compare November 22, 2025 12:11
@openjdk
Copy link

openjdk bot commented Nov 22, 2025

@wenshao Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. See OpenJDK Developers’ Guide for more information.

@wenshao wenshao force-pushed the datetime_unroll_2511 branch from 76bc921 to b254c01 Compare November 22, 2025 12:13
@openjdk
Copy link

openjdk bot commented Nov 22, 2025

@wenshao Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. See OpenJDK Developers’ Guide for more information.

@wenshao wenshao marked this pull request as draft November 22, 2025 12:16
@wenshao wenshao force-pushed the datetime_unroll_2511 branch 2 times, most recently from 7088d79 to 1fbfd3e Compare November 22, 2025 14:16
@wenshao wenshao force-pushed the datetime_unroll_2511 branch from 1fbfd3e to d82d940 Compare November 22, 2025 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build build-dev@openjdk.org core-libs core-libs-dev@openjdk.org i18n i18n-dev@openjdk.org

Development

Successfully merging this pull request may close these issues.

1 participant