Skip to content

Conversation

wenshao
Copy link
Contributor

@wenshao wenshao commented Aug 23, 2025

This PR introduces a new efficient API for appending two-digit integers to StringBuilders and refactors DateTimeHelper to leverage this new functionality.

Changes include:

  1. New appendPair method for efficient two-digit integer formatting (00-99):

    • Added AbstractStringBuilder.appendPair(int i) with core implementation
    • Added JavaLangAccess.appendPair(StringBuilder, int) for internal access
    • Added System.JavaLangAccessImpl.appendPair(StringBuilder, int) bridge
    • Added DecimalDigits.appendPair(StringBuilder, int) public static utility method
    • Enhanced Javadoc documentation for all new methods
  2. Refactored DateTimeHelper to use the new DecimalDigits.appendPair:

    • Updated DateTimeHelper.formatTo methods for LocalDate and LocalTime
    • Replaced manual formatting logic with the new efficient two-digit appending
    • Improved code clarity and consistency in date/time formatting

These changes improve code clarity and performance when formatting two-digit numbers, particularly in date/time formatting scenarios.


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

Issue

  • JDK-8366224: Introduce DecimalDigits.appendPair for efficient two-digit formatting and refactor DateTimeHelper (Enhancement - P4)

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 26911

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

Using diff file

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

Using Webrev

Link to Webrev Comment

wenshao and others added 2 commits August 23, 2025 10:47
…\n\nThis change adds a new internal API to efficiently append two-digit integers\n(00-99) to a StringBuilder. It includes:\n- AbstractStringBuilder.appendPair(int i): The core implementation.\n- JavaLangAccess.appendPair(StringBuilder, int): For internal access.\n- System.JavaLangAccessImpl.appendPair(StringBuilder, int): Bridge to AbstractStringBuilder.\n- DecimalDigits.appendPair(StringBuilder, int): Public static utility method.\n\nImproved Javadoc comments for clarity and consistency across all new methods.

Co-authored-by: Qwen-Coder <[email protected]>
… formatting\n\nThis change updates DateTimeHelper.formatTo methods for LocalDate and LocalTime\nto use the new DecimalDigits.appendPair method for formatting month, day, hour,\nminute, and second components. This improves code clarity and leverages the\nnewly introduced efficient two-digit integer appending functionality.

Co-authored-by: Qwen-Coder <[email protected]>
@bridgekeeper
Copy link

bridgekeeper bot commented Aug 23, 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 Aug 23, 2025

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

@openjdk
Copy link

openjdk bot commented Aug 23, 2025

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

  • core-libs
  • security

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.

Co-authored-by: Qwen-Coder <[email protected]>

Refactored the year formatting logic to use subtraction instead of modulo for calculating the lower two digits, which can be slightly more efficient. Added a comment to clarify the safety of the input range for DecimalDigits.appendPair.
@wenshao
Copy link
Contributor Author

wenshao commented Aug 23, 2025

I've run performance tests comparing the baseline (7b9969d) and this PR's branch (appendPair_202508) using the
make test TEST="micro:java.time.ToStringBench benchmark. The results show significant performance improvements across multiple time
formatting operations:

Performance Comparison Results

Benchmark Baseline (7b9969d) This PR (appendPair_202508) Improvement
instantToString 9.078 ± 0.504 ops/ms 12.904 ± 3.810 ops/ms ~42% faster
localDateTimeToString 15.666 ± 1.916 ops/ms 31.306 ± 5.758 ops/ms ~100% faster
localDateToString 23.257 ± 1.596 ops/ms 24.976 ± 2.456 ops/ms ~7.4% faster
localTimeToString 22.727 ± 1.991 ops/ms 37.266 ± 1.453 ops/ms ~64% faster
zonedDateTimeToString 13.094 ± 0.053 ops/ms 24.040 ± 8.511 ops/ms ~84% faster

The most significant improvements are seen in localDateTimeToString and localTimeToString operations, which are now
approximately 2x faster. This validates that the changes in this PR provide meaningful performance benefits for time
formatting operations.

Tests were run on macOS ARM64 The performance gains are consistent with the optimization goals of this PR.

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 code here looks reasonable to me, and the performance gain is nice.

@wenshao wenshao changed the title Introduce DecimalDigits.appendPair for efficient two-digit formatting and refactor DateTimeHelper 8366224: Introduce DecimalDigits.appendPair for efficient two-digit formatting and refactor DateTimeHelper Aug 27, 2025
@wenshao wenshao marked this pull request as ready for review August 27, 2025 09:10
@openjdk openjdk bot added the rfr Pull request is ready for review label Aug 27, 2025
@mlbridge
Copy link

mlbridge bot commented Aug 27, 2025

Webrevs

* The integer {@code v} is formatted as two decimal digits.
* If the value is between 0 and 9, it is formatted with a leading zero
* (e.g., 5 becomes "05"). If the value is outside the range 0-99,
* the behavior is unspecified.
Copy link
Member

Choose a reason for hiding this comment

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

Just out of interest, why leave the behaviour as unspecified instead of validating and throwing an exception?

@jdlib
Copy link

jdlib commented Aug 27, 2025

Nice performance gains, but still this is quite a complex solution with a narrow use case (optimizes toString() of some java.time classes).
What about broadening the scope?
Situation:
You want to build a Latin1 string and number of added chars is more or less known (so we can optimize byte[] allocations)

Solution sketch: Introduce a new Latin1Builder

  • owning a byte[] value and int count field
  • allowing only latin1 chars to be added
    Example:
	public static void formatTo(Latin1Builder buf, LocalDate date) {
		buf.ensureCapacity(10); // most common size
		int year  = date.getYear();
		if (year < 0)
			buf.append('-');
		else if (year > 9999)
			buf.append('+');
		buf.appendInt(year);
		buf.append('-');
		buf.appendIntPair(buf, date.getMonthValue());
		buf.append('-');
		buf.appendIntPair(buf, date.getDayOfMonth());
	}

@RogerRiggs
Copy link
Contributor

Generally, even with the performance benefits, this code is too narrow a use case to justify the hacking into AbstractStringBuilder and adding another package busting JavaLangAccess entry point.

This change modifies the toString() methods in MonthDay, YearMonth, ZoneOffset, and ChronoLocalDateImpl to use DecimalDigits.appendPair for formatting two-digit numbers. This provides a more efficient and consistent way to format these values.

Also added a comment in ChronoLocalDateImpl.toString() to explain why get() is used instead of getLong() for performance reasons, as the values are guaranteed to be within the int range for all chronologies.

Co-authored-by: Qwen-Coder <[email protected]>
@wenshao
Copy link
Contributor Author

wenshao commented Aug 28, 2025

Thank you for your feedback, @RogerRiggs.

I introduced this PR because I plan to optimize the performance of java.time.format.DateTimeFormatter's parse and format methods. This is a very large change, and to facilitate code review, I am splitting the process into multiple smaller PRs. This allows us to achieve continuous improvement, and this PR is one part of that effort.

In future optimizations, I need to use AbstractStringBuilder.appendPair when writing values like Year/Month/DayOfMonth/Hour/Minute/Second within the java.time.format.DateTimeFormatterBuilder$NumberPrinterParser#format method.

In the current PR, using appendPair in DateTimeHelper to write month/dayOfMonth/hour/minute/second results in code that is more intuitive than the original.

  • Original
month = date.getMonthValue(),
day   = date.getDayOfMonth();

buf.append(month < 10 ? "-0" : "-").append(month)
   .append(day < 10 ? "-0" : "-").append(day);
  • Improved
buf.append('-');
DecimalDigits.appendPair(buf, date.getMonthValue());
buf.append('-');
DecimalDigits.appendPair(buf, date.getDayOfMonth());

The improved code is more readable than the original.

Placing appendPair in DecimalDigits alongside other putPair series methods also makes the code's purpose easier to understand.

AbstractStringBuilder.appendPair was introduced for the java.time format scenario, but it can also be used in java.util.Formatter in the future, or potentially with StringTemplate if it's introduced. My latest commit adds more usage scenarios in core-libs.

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

Successfully merging this pull request may close these issues.

5 participants