On Wed, 26 Jun 2024 09:43:32 GMT, lingjun-cg <d...@openjdk.org> wrote:

>> ### Performance regression of DecimalFormat.format
>> From the output of perf, we can see the hottest regions contain atomic 
>> instructions.  But when run with JDK 11, there is no such problem. The 
>> reason is the removed biased locking.  
>> The DecimalFormat uses StringBuffer everywhere, and StringBuffer itself 
>> contains many synchronized methods.
>> So I added support for some new methods that accept StringBuilder which is 
>> lock-free.
>> 
>> ### Benchmark testcase
>> 
>> @BenchmarkMode(Mode.AverageTime)
>> @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
>> @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
>> @State(Scope.Thread)
>> @OutputTimeUnit(TimeUnit.NANOSECONDS)
>> public class JmhDecimalFormat {
>> 
>>     private DecimalFormat format;
>> 
>>     @Setup(Level.Trial)
>>     public void setup() {
>>         format = new DecimalFormat("#0.00000");
>>     }
>> 
>>     @Benchmark
>>     public void testNewAndFormat() throws InterruptedException {
>>         new DecimalFormat("#0.00000").format(9524234.1236457);
>>     }
>> 
>>     @Benchmark
>>     public void testNewOnly() throws InterruptedException {
>>         new DecimalFormat("#0.00000");
>>     }
>> 
>>     @Benchmark
>>     public void testFormatOnly() throws InterruptedException {
>>         format.format(9524234.1236457);
>>     }
>> }
>> 
>> 
>> ### Test result
>> #### Current JDK before optimize
>> 
>>  Benchmark                             Mode  Cnt    Score   Error  Units
>> JmhDecimalFormat.testFormatOnly       avgt   50  642.099 ? 1.253  ns/op
>> JmhDecimalFormat.testNewAndFormat     avgt   50  989.307 ? 3.676  ns/op
>> JmhDecimalFormat.testNewOnly          avgt   50  303.381 ? 5.252  ns/op
>> 
>> 
>> 
>> #### Current JDK after optimize
>> 
>> Benchmark                          Mode  Cnt    Score   Error  Units
>> JmhDecimalFormat.testFormatOnly    avgt   50  351.499 ? 0.761  ns/op
>> JmhDecimalFormat.testNewAndFormat  avgt   50  615.145 ? 2.478  ns/op
>> JmhDecimalFormat.testNewOnly       avgt   50  209.874 ? 9.951  ns/op
>> 
>> 
>> ### JDK 11 
>> 
>> Benchmark                          Mode  Cnt    Score   Error  Units
>> JmhDecimalFormat.testFormatOnly    avgt   50  364.214 ? 1.191  ns/op
>> JmhDecimalFormat.testNewAndFormat  avgt   50  658.699 ? 2.311  ns/op
>> JmhDecimalFormat.testNewOnly       avgt   50  248.300 ? 5.158  ns/op
>
> lingjun-cg has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   8333396: Performance regression of DecimalFormat.format

src/java.base/share/classes/java/text/Format.java line 193:

> 191: 
> 192:     StringBuilder format(Object obj,
> 193:                          StringBuilder toAppendTo,

Can we change this type to `StringBuf`, like `StringBuf format(Object obj, 
StringBuf toAppendTo, FieldPosition pos)`? Then we can remove a few 
`StringBuilder` overloads by just using the `StringBuf` version.

src/java.base/share/classes/java/text/NumberFormat.java line 424:

> 422: 
> 423:     StringBuilder format(double number,
> 424:                          StringBuilder toAppendTo,

Same remark, `StringBuf format(double number, StringBuf toAppendTo, 
FieldPosition pos)`

src/java.base/share/classes/java/text/SimpleDateFormat.java line 1034:

> 1032:     @Override
> 1033:     public AttributedCharacterIterator formatToCharacterIterator(Object 
> obj) {
> 1034:         StringBuilder sb = new StringBuilder();

On second thought, we can just make this `StringBuf sb = 
StringBufFactory.of(new StringBuilder())` - or just add `StringBufFactory.of()` 
which defaults to create a StringBuilder-backed buf.

src/java.base/share/classes/java/text/SimpleDateFormat.java line 1448:

> 1446:         numberFormat.setMaximumIntegerDigits(maxDigits);
> 1447:         if (buffer.isProxyStringBuilder()) {
> 1448:             numberFormat.format((long)value, buffer.asStringBuilder(), 
> DontCareFieldPosition.INSTANCE);

This `numberFormat` might be a user class; if that's the case, we might do 
something like:

if (buffer.isProxyStringBuilder()) {
    var nf = numberFormat; // field is protected, users can change it even with 
races!
    if (nf.isInternalSubclass()) {
        nf.format((long)value, buffer.asStringBuilder(), 
DontCareFieldPosition.INSTANCE);
    } else {
        // format to stringbuffer, and add that buffer to the builder
        buffer.append(nf.format((long)value, new StringBuffer(), 
DontCareFieldPosition.INSTANCE));
    }
} else { // existing code

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

PR Review Comment: https://git.openjdk.org/jdk/pull/19513#discussion_r1655595282
PR Review Comment: https://git.openjdk.org/jdk/pull/19513#discussion_r1655596623
PR Review Comment: https://git.openjdk.org/jdk/pull/19513#discussion_r1655605616
PR Review Comment: https://git.openjdk.org/jdk/pull/19513#discussion_r1655603917

Reply via email to