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