RFR: 8366224: Introduce DecimalDigits.appendPair for efficient two-digit formatting and refactor DateTimeHelper [v17]

Shaojin Wen swen at openjdk.org
Wed Nov 12 15:41:15 UTC 2025


On Wed, 12 Nov 2025 15:01:49 GMT, Shaojin Wen <swen at openjdk.org> wrote:

>> 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.appendLatin1(char c1, char c2)` with core implementation
>>    - Added `JavaLangAccess.appendPair(StringBuilder, char c1, char c2)` for internal access
>>    - 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.
>
> Shaojin Wen has updated the pull request incrementally with one additional commit since the last revision:
> 
>   static field JLA

In the current version, the methods newly added to JLA have been removed, and a new approach has been adopted to still achieve a significant performance improvement.

Below are the performance data for a MacBook M1 Pro:

* shell

make test TEST="micro:java.time.ToStringBench.localDateToString"


* output

# baseline 76a0732ba5c0f3159ed0ebc5fcb2dfb7117b38cd 
Benchmark                             Mode  Cnt   Score   Error   Units
ToStringBench.instantToString        thrpt   15   9.736 ± 1.054  ops/ms
ToStringBench.localDateTimeToString  thrpt   15  16.034 ± 2.344  ops/ms
ToStringBench.localDateToString      thrpt   15  61.926 ± 1.877  ops/ms
ToStringBench.localTimeToString      thrpt   15  21.297 ± 1.686  ops/ms
ToStringBench.zonedDateTimeToString  thrpt   15  15.536 ± 2.785  ops/ms



# pr_26911 ae1fcb6b80fa6c153202f2c26fa459bb9ac08990
Benchmark                             Mode  Cnt    Score   Error   Units
ToStringBench.instantToString        thrpt   15   10.925 ± 1.160  ops/ms
ToStringBench.localDateTimeToString  thrpt   15   19.034 ± 2.861  ops/ms
ToStringBench.localDateToString      thrpt   15  103.126 ± 1.932  ops/ms
ToStringBench.localTimeToString      thrpt   15   29.642 ± 1.904  ops/ms
ToStringBench.zonedDateTimeToString  thrpt   15   30.409 ± 5.503  ops/ms


* compare

| Benchmark             | Baseline (ops/ms) | PR #26911 (ops/ms) | Improvement (%) |
|-----------------------|-------------------|--------------------|-----------------|
| instantToString       | 9.736 ± 1.054     | 10.925 ± 1.160     | +12.2%          |
| localDateTimeToString | 16.034 ± 2.344    | 19.034 ± 2.861     | +18.7%          |
| localDateToString     | 61.926 ± 1.877    | 103.126 ± 1.932    | +66.5%          |
| localTimeToString     | 21.297 ± 1.686    | 29.642 ± 1.904     | +39.2%          |
| zonedDateTimeToString | 15.536 ± 2.785    | 30.409 ± 5.503     | +95.7%          |


This primarily involves triggering Late Inline in the `appendPair`/`appendQuad` methods of `DecimalDigits`, and then optimizing `MergeStore` and `Elimate Allocation` accordingly. See below:


@ 38   java.lang.StringBuilder::append (8 bytes)   inline (hot)   late inline succeeded (string method)


And it can trigger the MergeStore optimization of DecimalDigits::appendPair:
* JVM Args

-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=TraceMergeStores,jdk.internal.util.DecimalDigits::appendPair,SUCCESS


[TraceMergeStores]: Replace
 796  StoreB  === 818 761 788 35  [[ 801 ]]  @byte[int:>=0] (java/lang/Cloneable,java/io/Serializable):NotNull:exact+any *, idx=7;  Memory: @byte[int:>=0] (java/lang/Cloneable,java/io/Serializable):NotNull:exact+any *, idx=7;
 801  StoreB  === 818 796 799 92  [[ 810 ]]  @byte[int:>=0] (java/lang/Cloneable,java/io/Serializable):NotNull:exact+any *, idx=7;  Memory: @byte[int:>=0] (java/lang/Cloneable,java/io/Serializable):NotNull:exact+any *, idx=7;
[TraceMergeStores]: with
  35  LoadS  === 5 7 33  [[ 92 488 570 808 796 470 258 604 644 836 ]]  @short[int:>=0] (java/lang/Cloneable,java/io/Serializable):exact+any *, idx=4; #short !jvms: DecimalDigits::appendPair @ bci:7 (line 463)
 836  StoreC  === 818 761 788 35  [[ ]]  @byte[int:>=0] (java/lang/Cloneable,java/io/Serializable):NotNull:exact+any *, idx=7; mismatched  Memory: @byte[int:>=0] (java/lang/Cloneable,java/io/Serializable):NotNull:exact+any *, idx=7;
 ```


-XX:+UnlockDiagnosticVMOptions -XX:+PrintEliminateAllocations

The following log was observed:
 ```
 Scalar  169  CheckCastPP  === 160 158  [[ 570 488 470 258 ]]  #java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=146  Oop:java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=146 !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465)
++++ Eliminated: 146 Allocate
Scalar   77  CheckCastPP  === 71 69  [[ 570 488 644 604 258 470 ]]  #byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=57 !orig=[326] !jvms: DecimalDigits::appendPair @ bci:14 (line 464)
++++ Eliminated: 57 AllocateArray
NotScalar (Object is referenced by node) 1147  CheckCastPP  === 1141 1139  [[ 1271 1154 1154 1203 1159 1159 1685 1647 ]]  #byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=1127 !orig=[1371] !jvms: DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:68 (line 66)
  >>>>  1271  EncodeP  === _ 1147  [[ 1273 ]]  #narrowoop: byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=1127 !orig=[1370] !jvms: String::<init> @ bci:6 (line 5012) String::newStringWithLatin1Bytes @ bci:22 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:68 (line 66)
NotScalar (Object is referenced by node) 2071  CheckCastPP  === 2065 2063  [[ 2195 2078 2078 2127 2083 2083 2609 2571 ]]  #byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=2051 !orig=[2295] !jvms: DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:83 (line 68)
  >>>>  2195  EncodeP  === _ 2071  [[ 2197 ]]  #narrowoop: byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=2051 !orig=[2294] !jvms: String::<init> @ bci:6 (line 5012) String::newStringWithLatin1Bytes @ bci:22 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:83 (line 68)
Scalar  2150  CheckCastPP  === 2141 2139  [[ 2537 2455 2437 ]]  #java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=2127  Oop:java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=2127 !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:83 (line 68)
++++ Eliminated: 2127 Allocate
Scalar   286  CheckCastPP  === 277 275  [[ 681 599 581 371 ]]  #java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=263  Oop:java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=263 !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendQuad @ bci:71 (line 487) DateTimeHelper::formatTo @ bci:30 (line 58)
++++ Eliminated: 263 Allocate
Scalar   182  CheckCastPP  === 176 174  [[ 681 191 191 198 198 205 205 599 216 216 755 715 371 581 ]]  #byte[int:4] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=162 !orig=[438] !jvms: DecimalDigits::appendQuad @ bci:36 (line 486) DateTimeHelper::formatTo @ bci:30 (line 58)
++++ Eliminated: 162 AllocateArray
Scalar  1226  CheckCastPP  === 1217 1215  [[ 1613 1531 1513 ]]  #java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=1203  Oop:java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=1203 !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:68 (line 66)
++++ Eliminated: 1203 Allocate
Scalar  1147  CheckCastPP  === 1141 1139  [[ 1613 1154 1154 1531 1159 1159 1685 1647 1513 ]]  #byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=1127 !orig=[1371] !jvms: DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:68 (line 66)
++++ Eliminated: 1127 AllocateArray
Scalar  2071  CheckCastPP  === 2065 2063  [[ 2537 2078 2078 2455 2083 2083 2609 2571 2437 ]]  #byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=2051 !orig=[2295] !jvms: DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:83 (line 68)
++++ Eliminated: 2051 AllocateArray
NotScalar (Object is referenced by node)  505  CheckCastPP  === 502 723  [[ 1027 227 ]]  #byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=226 !orig=[2285] !jvms: DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:68 (line 66) LocalDate::toString @ bci:12 (line 2150)
  >>>>  1027  EncodeP  === _ 505  [[ 973 ]]  #narrowoop: byte[int:2] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=226 !orig=[2284] !jvms: String::<init> @ bci:6 (line 5012) String::newStringWithLatin1Bytes @ bci:22 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:68 (line 66) LocalDate::toString @ bci:12 (line 2150)
Scalar   232  Allocate  === 513 514 515 41 1 (459 460 317 1 1 1 188 1 1 1 1 1 1 1 188 1 1 516 1 ) [[ 474 253 120 937 337 603 ]]  rawptr:NotNull ( int:>=0, java/lang/Object:NotNull *, bool, top, bool ) allocationKlass:java/lang/String String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:83 (line 68) LocalDate::toString @ bci:12 (line 2150) !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:83 (line 68) LocalDate::toString @ bci:12 (line 2150)
++++ Eliminated: 232 Allocate
Scalar   222  Allocate  === 491 492 493 41 1 (459 460 317 1 1 1 188 188 189 1 1 1 1 1 1 188 1 1 494 1 ) [[ 464 249 112 920 384 589 ]]  rawptr:NotNull ( int:>=0, java/lang/Object:NotNull *, bool, top, bool ) allocationKlass:java/lang/String String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendQuad @ bci:71 (line 487) DateTimeHelper::formatTo @ bci:30 (line 58) LocalDate::toString @ bci:12 (line 2150) !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendQuad @ bci:71 (line 487) DateTimeHelper::formatTo @ bci:30 (line 58) LocalDate::toString @ bci:12 (line 2150)
++++ Eliminated: 222 Allocate
Scalar   227  Allocate  === 502 503 504 41 1 (459 460 317 1 1 1 188 188 189 1 1 1 1 1 188 1 1 505 1 ) [[ 469 251 116 972 389 643 ]]  rawptr:NotNull ( int:>=0, java/lang/Object:NotNull *, bool, top, bool ) allocationKlass:java/lang/String String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:68 (line 66) LocalDate::toString @ bci:12 (line 2150) !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendPair @ bci:33 (line 465) DateTimeHelper::formatTo @ bci:68 (line 66) LocalDate::toString @ bci:12 (line 2150)
++++ Eliminated: 227 Allocate
Scalar   494  CheckCastPP  === 491 714  [[ 555 1011 1011 903 903 764 764 555 ]]  #byte[int:4] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=221 !orig=[2966] !jvms: DecimalDigits::appendQuad @ bci:36 (line 486) DateTimeHelper::formatTo @ bci:30 (line 58) LocalDate::toString @ bci:12 (line 2150)
++++ Eliminated: 221 AllocateArray
Scalar   231  AllocateArray  === 502 503 511 41 1 (459 446 317 344 487 1 188 1 1 1 1 1 1 512 188 490 ) [[ 473 252 119 898 514 732 ]]  rawptr:NotNull ( int:>=0, java/lang/Object:NotNull *, bool, int, bool ) allocationKlass:[B DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:83 (line 68) LocalDate::toString @ bci:12 (line 2150) !jvms: DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:83 (line 68) LocalDate::toString @ bci:12 (line 2150)
++++ Eliminated: 231 AllocateArray
Scalar   188  CheckCastPP  === 435 436  [[ 220 220 220 226 543 226 226 550 550 200 88 86 86 543 88 ]]  #java/lang/StringBuilder (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/Appendable):NotNull:exact *,iid=219  Oop:java/lang/StringBuilder (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/Appendable):NotNull:exact *,iid=219 !jvms: LocalDate::toString @ bci:0 (line 2149)
++++ Eliminated: 219 Allocate
Scalar   226  AllocateArray  === 488 185 500 41 1 (459 446 317 344 487 1 3142 3142 189 1 1 1 1 501 3142 490 759 317 317 671 ) [[ 468 250 115 894 503 723 ]]  rawptr:NotNull ( int:>=0, java/lang/Object:NotNull *, bool, int, bool ) allocationKlass:[B DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:68 (line 66) LocalDate::toString @ bci:12 (line 2150) !jvms: DecimalDigits::appendPair @ bci:14 (line 464) DateTimeHelper::formatTo @ bci:68 (line 66) LocalDate::toString @ bci:12 (line 2150)
++++ Eliminated: 226 AllocateArray
NotScalar (Object is passed as argument)  759  CheckCastPP  === 542 747  [[ 200 557 1601 1601 1597 1597 1571 1571 1605 1605 557 1665 558 1665 1650 1650 558 1635 197 1635 153 858 1620 1620 86 88 ]]  #byte[int:10] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=220 !orig=[386] !jvms: AbstractStringBuilder::<init> @ bci:12 (line 101) StringBuilder::<init> @ bci:2 (line 119) LocalDate::toString @ bci:6 (line 2149)
  >>>>   858  ArrayCopy  === 990 1 991 41 1 (759 769 989 769 1585 _ _ _ _ ) [[ 683 859 ]]  void ( java/lang/Object *, int, java/lang/Object *, int, int, int, int, BotPTR *+bot, BotPTR *+bot ) (oop array clone, tightly coupled allocation) !jvms: Arrays::copyOfRange @ bci:11 (line 3843) String::<init> @ bci:32 (line 4995) StringBuilder::toString @ bci:16 (line 478) LocalDate::toString @ bci:16 (line 2151)
NotScalar (Object is passed as argument)  759  CheckCastPP  === 542 747  [[ 200 557 1601 1601 1597 1597 1571 1571 1605 1605 557 1665 558 1665 1650 1650 558 1635 197 1635 153 858 1620 1620 86 88 ]]  #byte[int:10] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=220 !orig=[386] !jvms: AbstractStringBuilder::<init> @ bci:12 (line 101) StringBuilder::<init> @ bci:2 (line 119) LocalDate::toString @ bci:6 (line 2149)
  >>>>   858  ArrayCopy  === 990 1 991 41 1 (759 769 989 769 1585 _ _ _ _ ) [[ 683 859 ]]  void ( java/lang/Object *, int, java/lang/Object *, int, int, int, int, BotPTR *+bot, BotPTR *+bot ) (oop array clone, tightly coupled allocation) !jvms: Arrays::copyOfRange @ bci:11 (line 3843) String::<init> @ bci:32 (line 4995) StringBuilder::toString @ bci:16 (line 478) LocalDate::toString @ bci:16 (line 2151)
Scalar  211  CheckCastPP  === 202 200  [[ 610 528 510 298 ]]  #java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=188  Oop:java/lang/String (java/io/Serializable,java/lang/Comparable,java/lang/CharSequence,java/lang/constant/Constable,java/lang/constant/ConstantDesc):NotNull:exact *,iid=188 !jvms: String::newStringWithLatin1Bytes @ bci:16 (line 782) System$1::uncheckedNewStringWithLatin1Bytes @ bci:1 (line 2133) DecimalDigits::appendQuad @ bci:71 (line 487)
++++ Eliminated: 188 Allocate
Scalar  107  CheckCastPP  === 101 99  [[ 610 116 116 123 123 130 130 528 141 141 644 684 298 510 ]]  #byte[int:4] (java/lang/Cloneable,java/io/Serializable):NotNull:exact *,iid=87 !orig=[366] !jvms: DecimalDigits::appendQuad @ bci:36 (line 486)
++++ Eliminated: 87 AllocateArray

-------------

PR Comment: https://git.openjdk.org/jdk/pull/26911#issuecomment-3522578182


More information about the core-libs-dev mailing list