Skip to content

8337279: Share StringBuilder to format instant#20353

Closed
wenshao wants to merge 18 commits intoopenjdk:masterfrom
wenshao:optim_instant_fmt_202407
Closed

8337279: Share StringBuilder to format instant#20353
wenshao wants to merge 18 commits intoopenjdk:masterfrom
wenshao:optim_instant_fmt_202407

Conversation

@wenshao
Copy link
Contributor

@wenshao wenshao commented Jul 26, 2024

  1. Create a tool class jdk.internal.util.DateTimeHelper, move the formatTo method of LocalDateTime/LocalDate/LocalTime to it, so that these methods can be used across packages within JDK, so that StringBuilder can be shared, avoiding multiple creation of StringBuilder and toString.
  2. Refactor DateTimeFormatterBuilder::format to use the jdk.internal.util.DateTimeHelper::formatTo method.
  3. Split the DateTimeFormatterBuilder::format method, separate the currentEra and beforeCurrentEra sub-methods, so that codeSize < 325 can be inlined. Most scenarios call currentEra, so performance is improved.

Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 2 Reviewers)

Issue

  • JDK-8337279: Share StringBuilder to format instant (Enhancement - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 20353

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

Using diff file

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

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 26, 2024

👋 Welcome back wenshao! 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 Jul 26, 2024

@wenshao This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8337279: Share StringBuilder to format instant

Reviewed-by: naoto, liach

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 613 new commits pushed to the master branch:

  • 22182f7: 8352112: [ubsan] hotspot/share/code/relocInfo.cpp:130:37: runtime error: applying non-zero offset 18446744073709551614 to null pointer
  • c2e14b1: 8343891: Test javax/swing/JTabbedPane/TestJTabbedPaneBackgroundColor.java failed
  • 52c6ce6: 8352091: GenShen: assert(!(request.generation->is_old() && _heap->old_generation()->is_doing_mixed_evacuations())) failed: Old heuristic should not request cycles while it waits for mixed evacuation
  • b8f3856: 8352420: [ubsan] codeBuffer.cpp:984:27: runtime error: applying non-zero offset 18446744073709486080 to null pointer
  • 0cb110e: 8350892: [JVMCI] Align ResolvedJavaType.getInstanceFields with Class.getDeclaredFields
  • 04eac0c: 8352159: RISC-V: add more zfa support
  • ac760dd: 8352423: RISC-V: simplify DivI/L ModI/L
  • d1cf232: 8352248: Check if CMoveX is supported
  • 2b55979: 8352529: RISC-V: enable loopopts tests
  • 8469458: 8352511: Show additional level of headings in table of contents
  • ... and 603 more: https://git.openjdk.org/jdk/compare/f98d9a330128302207fb66dfa2555885ad93135f...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk
Copy link

openjdk bot commented Jul 26, 2024

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

  • 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.

@openjdk openjdk bot added core-libs core-libs-dev@openjdk.org i18n i18n-dev@openjdk.org labels Jul 26, 2024
@wenshao
Copy link
Contributor Author

wenshao commented Jul 26, 2024

By removing redundant code logic, the codeSize can be reduced from 444 to 240, and the performance is improved.

Below are the performance numbers running on MacBook M1 Pro

-# master 5ff7c57f9ff5428ef3d2aedd7e860bb1e8ff29ea
-Benchmark                       Mode  Cnt  Score   Error   Units
-ToStringBench.instantToString  thrpt   15  4.734 ? 0.547  ops/ms +24.10%

+# current baf0f9ca83c111401d6e034dfba0ee2c71280e62
+Benchmark                       Mode  Cnt  Score   Error   Units
+ToStringBench.instantToString  thrpt   15  5.875 ? 0.301  ops/ms

@wenshao wenshao changed the title Optimize format instant 8337279: Optimize format instant Jul 26, 2024
@openjdk openjdk bot added the rfr Pull request is ready for review label Jul 26, 2024
@mlbridge
Copy link

mlbridge bot commented Jul 26, 2024

}
// add fraction
if ((fractionalDigits < 0 && inNano > 0) || fractionalDigits > 0) {
if (fractionalDigits > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks the logic. fractionalDigits can be negative in the block below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If fractionalDigits < 0, printNano is implemented in LocalDateTime

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fractionalDigits == -2 is used to output 0, 3, 6 or 9 fractional digits as needed. This can be handled by LocalDateTime.

fractionalDigits == -1 is used to output as many fractional digits as needed, from 0 to 9 digits. This is NOT handled by LocalDateTime.

Furthermore, if the -2 branch is handled by LocalDateTime then the logic below for -2 is redundant.

If the tests did not fail as a result of this change, then I imagine the tests need improving.

@plokhotnyuk
Copy link
Contributor

@jodastephen @wenshao Could you please review these proposed changes for instantiation of Instant values with already normalized nanos?

@RogerRiggs
Copy link
Contributor

Could you please review these proposed changes for instantiation of Instant values with already normalized nanos?

@plokhotnyuk Your contribution cannot be acted upon until the OCA is confirmed.
If you have not submitted an OCA, please do so. See https://oca.opensource.oracle.com/

@plokhotnyuk
Copy link
Contributor

Could you please review these proposed changes for instantiation of Instant values with already normalized nanos?

@plokhotnyuk Your contribution cannot be acted upon until the OCA is confirmed. If you have not submitted an OCA, please do so. See https://oca.opensource.oracle.com/

@RogerRiggs Thanks for your support!

I've submitted a signed OCA more than year ago and nobody answered me yet.

Today's attempt shows the following notification:
image

}
// add fraction
if ((fractionalDigits < 0 && inNano > 0) || fractionalDigits > 0) {
if (fractionalDigits > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fractionalDigits == -2 is used to output 0, 3, 6 or 9 fractional digits as needed. This can be handled by LocalDateTime.

fractionalDigits == -1 is used to output as many fractional digits as needed, from 0 to 9 digits. This is NOT handled by LocalDateTime.

Furthermore, if the -2 branch is handled by LocalDateTime then the logic below for -2 is redundant.

If the tests did not fail as a result of this change, then I imagine the tests need improving.

}
}
// use LocalDateTime.toString, If fractionalDigits < 0, printNano is implemented in LocalDateTime
LocalDateTime ldt = LocalDateTime.ofEpochSecond(inSecs, fractionalDigits >= 0 ? 0 : inNano, ZoneOffset.UTC);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this whole approach is flawed. The comment above says:

// use INSTANT_SECONDS, thus this code is not bound by Instant.MAX

which indicates that the instant value may not fit in LocalDateTime.

You may be able to special case certain code paths to use LocalDateTime, but that makes things more complex, and not necessarily faster.

2. Split two methods to make codeSize less than 325
@wenshao
Copy link
Contributor Author

wenshao commented Jul 28, 2024

@jodastephen thank you for your patience in answering my questions.

Now I've used a new way to implement it, splitting it into two sub-methods without changing the original logic, which also makes codeSize < 325, and the performance is also faster than before. Here are the performance numbers running on a MacBook M1 Pro:

- # master 5ff7c57f9ff5428ef3d2aedd7e860bb1e8ff29ea
-Benchmark                       Mode  Cnt  Score   Error   Units
-ToStringBench.instantToString  thrpt   15  4.558 ? 0.561  ops/ms

+ # current 757984258257fd82c389f3ee7bd9be927375e2e0
+Benchmark                       Mode  Cnt  Score   Error   Units
+ToStringBench.instantToString  thrpt   15  5.564 ? 0.465  ops/ms +22.07%

I also added relevant tests to prevent errors from being discovered in future related changes.

I hope to reuse LocalDateTime.toString for the most common paths, so that we can also use LocalDateTime.formatTo method, which can reduce object allocation.

I plan to continue to optimize the formatTo method of LocalTime and LocalDate, so that InstantPrinterParser.format can also benefit from it.

@wenshao wenshao requested a review from jodastephen August 8, 2024 05:09
Copy link
Contributor

@jodastephen jodastephen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still skeptical of some parts of this PR as it makes the code harder to folow. The new tests are a good addition and should be merged.

Have you tried the performance of simply breaking out the currentEra/beforeCurrentEra methods without making any other changes? Since the logic to produce the nano fraction is going to stay in this method, I don't really see the advantage in trying to get LocalDateTime to do the fraction some of the time.

wenshao and others added 2 commits August 11, 2024 18:09
…Builder.java

Co-authored-by: Stephen Colebourne <scolebourne@joda.org>
@jodastephen
Copy link
Contributor

A more productive direction might be to move LocalDate.formatTo and LocalTime.formatTo to a JDK internal class (eg. in jdk.internal.util?), and then edit LocalTime.formatTo to handle fractionalDigits directly as another method parameter. This would allow the formatters in DateTimeFormatterBuilder to directly use the formatTo methods without adding any new public APIs.

@wenshao
Copy link
Contributor Author

wenshao commented Aug 11, 2024

I'm still skeptical of some parts of this PR as it makes the code harder to folow. The new tests are a good addition and should be merged.

Have you tried the performance of simply breaking out the currentEra/beforeCurrentEra methods without making any other changes? Since the logic to produce the nano fraction is going to stay in this method, I don't really see the advantage in trying to get LocalDateTime to do the fraction some of the time.

git remote add wenshao git@github.com:wenshao/jdk.git
git fetch wenshao

# baseline with test
git checkout 85ee5de07dd9f45a4e1659e17f9a52e69fa77df3
make test TEST="micro:java.time.ToStringBench.instantToString"

Benchmark                       Mode  Cnt  Score   Error   Units
ToStringBench.instantToString  thrpt   15  4.749 ? 0.624  ops/ms

# current version
git checkout 875666143e6d717634f2a3cc3c397b2ca8b63e63
make test TEST="micro:java.time.ToStringBench.instantToString"

Benchmark                       Mode  Cnt  Score   Error   Units
ToStringBench.instantToString  thrpt   15  5.713 ? 0.474  ops/ms +20.29%

# baseline with simply breaking out the currentEra/beforeCurrentEra methods without making any other changes
git fbf66307f738cd40e061c6d26fa05c062ccd048b
make test TEST="micro:java.time.ToStringBench.instantToString"

Benchmark                       Mode  Cnt  Score   Error   Units
ToStringBench.instantToString  thrpt   15  4.716 ? 0.569  ops/ms -0.69%

In the branch fbf66307f738cd40e061c6d26fa05c062ccd048b, the benchmark results are very unstable because the test data includes currentEra and beforeCurrentEra. The current version has the best performance and can handle currentEra and beforeCurrentEra

The result is somewhat unexpected. I thought the performance improvement was mainly due to codeSize < 325. The specific reason needs further analysis.

@wenshao
Copy link
Contributor Author

wenshao commented Aug 11, 2024

A more productive direction might be to move LocalDate.formatTo and LocalTime.formatTo to a JDK internal class (eg. in jdk.internal.util?), and then edit LocalTime.formatTo to handle fractionalDigits directly as another method parameter. This would allow the formatters in DateTimeFormatterBuilder to directly use the formatTo methods without adding any new public APIs.

I prefer to provide JavaTimeAccess in SharedSecrets, which will require less changes. This depends on #20368

Copy link
Contributor

@jodastephen jodastephen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change looks OK from my perspective, however I would want someone with experience of SharedSecrets to comment

@wenshao
Copy link
Contributor Author

wenshao commented Jan 30, 2025

/open

@openjdk openjdk bot reopened this Jan 30, 2025
@openjdk
Copy link

openjdk bot commented Jan 30, 2025

@wenshao This pull request is now open

* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.util;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe jdk.internal.time would be appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, since we only have the DateTimeHelper class, we can wait until there are two or more classes before creating the jdk.internal.time package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This utility class can be final, and have a private default constructor so that no instance can be created.

import static java.time.LocalTime.NANOS_PER_SECOND;
import static java.time.LocalTime.SECONDS_PER_DAY;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
import static jdk.internal.util.DateTimeHelper.formatTo;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not import static across package boundaries. I discourage it even within the same package.
It makes the code hard to read and follow where the function is coming from.

@wenshao
Copy link
Contributor Author

wenshao commented Feb 1, 2025

I don't think using SharedSecret just for performance improvement and code size reduction is the right way, as the class is the last resort as the header says:

 * <p><strong>
 * Usage of these APIs often means bad encapsulation designs,
 * increased complexity and lack of sustainability.
 * Use this only as a last resort!
 * </strong>

@naotoj
I have removed SharedSecrets, can you review it again?

@wenshao wenshao requested a review from RogerRiggs February 1, 2025 00:40
Copy link
Member

@naotoj naotoj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good with some minor nits

* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.util;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This utility class can be final, and have a private default constructor so that no instance can be created.

bh.consume(instant.toString());
}
}
} No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a new line at the end

@wenshao
Copy link
Contributor Author

wenshao commented Feb 3, 2025

Thank you @naotoj, these have been fixed, please help review again

Copy link
Member

@naotoj naotoj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I confirmed the changes passed tier1-3 tests.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Feb 4, 2025
@AlanBateman
Copy link
Contributor

/reviewers 2 reviewer

@openjdk
Copy link

openjdk bot commented Feb 5, 2025

@AlanBateman
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 2 Reviewers).

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Feb 5, 2025
@bridgekeeper
Copy link

bridgekeeper bot commented Mar 5, 2025

@wenshao This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@wenshao
Copy link
Contributor Author

wenshao commented Mar 9, 2025

Keep it alive.

@wenshao wenshao changed the title 8337279: Optimize format instant 8337279: Share StringBuilder to format instant Mar 21, 2025
Copy link
Member

@liach liach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor looks harmless. Optimization is splitting methods to help profiling and avoid one string copy in ZonedDateTime.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Mar 22, 2025
@wenshao
Copy link
Contributor Author

wenshao commented Mar 22, 2025

/integrate

@openjdk
Copy link

openjdk bot commented Mar 22, 2025

Going to push as commit 7442039.
Since your change was applied there have been 613 commits pushed to the master branch:

  • 22182f7: 8352112: [ubsan] hotspot/share/code/relocInfo.cpp:130:37: runtime error: applying non-zero offset 18446744073709551614 to null pointer
  • c2e14b1: 8343891: Test javax/swing/JTabbedPane/TestJTabbedPaneBackgroundColor.java failed
  • 52c6ce6: 8352091: GenShen: assert(!(request.generation->is_old() && _heap->old_generation()->is_doing_mixed_evacuations())) failed: Old heuristic should not request cycles while it waits for mixed evacuation
  • b8f3856: 8352420: [ubsan] codeBuffer.cpp:984:27: runtime error: applying non-zero offset 18446744073709486080 to null pointer
  • 0cb110e: 8350892: [JVMCI] Align ResolvedJavaType.getInstanceFields with Class.getDeclaredFields
  • 04eac0c: 8352159: RISC-V: add more zfa support
  • ac760dd: 8352423: RISC-V: simplify DivI/L ModI/L
  • d1cf232: 8352248: Check if CMoveX is supported
  • 2b55979: 8352529: RISC-V: enable loopopts tests
  • 8469458: 8352511: Show additional level of headings in table of contents
  • ... and 603 more: https://git.openjdk.org/jdk/compare/f98d9a330128302207fb66dfa2555885ad93135f...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Mar 22, 2025
@openjdk openjdk bot closed this Mar 22, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Mar 22, 2025
@openjdk
Copy link

openjdk bot commented Mar 22, 2025

@wenshao Pushed as commit 7442039.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core-libs core-libs-dev@openjdk.org i18n i18n-dev@openjdk.org integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

9 participants