<i18n dev> RFR: 8333396: Performance regression of DecimalFormat.format [v12]
Chen Liang
liach at openjdk.org
Wed Jun 26 22:55:14 UTC 2024
On Wed, 26 Jun 2024 09:43:32 GMT, lingjun-cg <duke at 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
More information about the i18n-dev
mailing list