Skip to content

Conversation

@wenshao
Copy link
Contributor

@wenshao wenshao commented Sep 4, 2023

BigDecimal is a commonly used class in business development, It is often necessary to perform toString or toPlainString operations on BigDecimal.

The current version uses StringBuilder resulting in multiple memory allocations, I made a modification to improve performance.

Because BigDecimal uses stringCache to cache the result of toString, the performance of toString needs special treatment before testing, such as clearing stringCache by unsafe operation before each test, The performance of toString is similar to that of toEngineering.

The performance data is as follows:

1. benchmark script

sh make/devkit/createJMHBundle.sh
bash configure --with-jmh=build/jmh/jars
make test TEST="micro:java.math.BigDecimals.*ToPlainString"
make test TEST="micro:java.math.BigDecimals.*ToEngineering"

2. benchmark environment

3. benchmark result

-BigDecimals.testHugeToPlainString         avgt   15   188.691 ±  0.822  ns/op  (baseline)
-BigDecimals.testLargeToPlainString        avgt   15    36.656 ±  0.065  ns/op
-BigDecimals.testSmallToPlainString        avgt   15    34.342 ±  0.068  ns/op
-BigDecimals.testToPlainString             avgt   15  1719.494 ± 24.886  ns/op

+Benchmark                                 Mode  Cnt     Score    Error  Units (optimize)
+BigDecimals.testHugeToPlainString         avgt   15   133.972 ?  0.328  ns/op (+40.84%)
+BigDecimals.testLargeToPlainString        avgt   15    14.957 ?  0.047  ns/op (145.07%)
+BigDecimals.testSmallToPlainString        avgt   15    12.045 ?  0.036  ns/op (+185.11)
+BigDecimals.testToPlainString             avgt   15  1643.500 ?  3.217  ns/op (+4.62%)

-Benchmark                                 Mode  Cnt     Score    Error  Units (baseline)
-BigDecimals.testHugeToEngineeringString   avgt   15   207.621 ±  5.018  ns/op
-BigDecimals.testLargeToEngineeringString  avgt   15    35.658 ±  3.144  ns/op
-BigDecimals.testSmallToEngineeringString  avgt   15    15.142 ±  0.053  ns/op
-BigDecimals.testToEngineeringString       avgt   15  1813.959 ± 12.842  ns/op

+Benchmark                                 Mode  Cnt     Score    Error  Units (optimize)
+BigDecimals.testHugeToEngineeringString   avgt   15   142.110 ?  0.987  ns/op (+45.09%)
+BigDecimals.testLargeToEngineeringString  avgt   15    12.509 ?  0.056  ns/op (+185.05%)
+BigDecimals.testSmallToEngineeringString  avgt   15    10.579 ?  0.066  ns/op (+43.13%)
+BigDecimals.testToEngineeringString       avgt   15  1646.783 ?  3.060  ns/op (+10.15%)

Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8315585: Optimization for decimal to string (Enhancement - P4)

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 15555

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

Using diff file

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

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Sep 4, 2023

👋 Welcome back wenshao! 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 openjdk bot added the rfr Pull request is ready for review label Sep 4, 2023
@openjdk
Copy link

openjdk bot commented Sep 4, 2023

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

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@mlbridge
Copy link

mlbridge bot commented Sep 4, 2023

@wenshao
Copy link
Contributor Author

wenshao commented Sep 4, 2023

@cl4es can you help me to review this PR?

@wenshao
Copy link
Contributor Author

wenshao commented Sep 5, 2023

@liach can you help me to review this PR?

@cl4es
Copy link
Member

cl4es commented Sep 5, 2023

I think there is way too much duplication of logic going on here, primarily with StringLatin1.getChars(long, int, byte[]) (assuming #14699 is accepted in the current state where Integer.getChars(int, int, byte[]) and Long.getChars(...) is moved there). Perhaps the bulk of this could be moved to StringLatin1 and then exposed via e.g. JavaLangAccess.

On the motivation side I think it'd be helpful if you could point to some larger benchmarks, frameworks et.c. where optimizing BigDecimal::toString makes a significant difference. There has been some recent optimizations that remove the need for BigDecimal::toString for some code paths, e.g., #9410 - does this alleviate the need for this optimization or is this still useful or even critical?

@wenshao
Copy link
Contributor Author

wenshao commented Sep 6, 2023

Reducing code duplication with JavaLangAccess is a good suggestion, I'm waiting for #14699 to be merged.

I am the author of the json library fastjson and fastjson2. When serializing BigDecimal type data to JSON, I found that BigDecimal has performance improvements.

Including the following methods:

  • toString
  • toPlainString
  • new BigDecimal(String)

These optimizations are not only useful for fastjson and fastjson2, but also for gson/jackson and many other libraries.

The optimization of #9410 is doubleValue, which is not related to toString/toPlainString.

@jddarcy
Copy link
Member

jddarcy commented Sep 6, 2023

/reviewers 2

@openjdk
Copy link

openjdk bot commented Sep 6, 2023

@jddarcy
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@jddarcy
Copy link
Member

jddarcy commented Sep 6, 2023

Besides a review of the performance changes, myself or someone else who works on BigInteger and BigDecimal will need to review the changes.

From a quick look, it may be appropriate to add addition test cases to explicitly exercise the new code paths.

@wenshao
Copy link
Contributor Author

wenshao commented Sep 6, 2023

I have added test, set breakpoints by debugging to ensure that all branches of changed code have been tested.

@wenshao
Copy link
Contributor Author

wenshao commented Sep 8, 2023

@cl4es I have pushed a new commit to reduce duplicate code

Copy link
Member

@cl4es cl4es left a comment

Choose a reason for hiding this comment

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

Seems like a step in the right direction w.r.t. code duplication. There's still a lot going on in this PR so it'll take some time to digest. Is there some way to split this into a series of enhancements for easier review? The jla.newStringLatin1NoRepl additions could be split out and done first - along with evidence that it helps in other places like UUID and HexFormat.

@wenshao
Copy link
Contributor Author

wenshao commented Sep 8, 2023

The new performance data is as follows:

1. benchmark script

sh make/devkit/createJMHBundle.sh
bash configure --with-jmh=build/jmh/jars
make test TEST="micro:java.math.BigDecimals.*ToPlainString"
make test TEST="micro:java.math.BigDecimals.*ToEngineering"

2. benchmark environment

3. benchmark result

3.1. aliyun_ecs_c8i.xlarge

  • cpu : intel xeon sapphire rapids (x64)
  • os : Alibaba Cloud Linux 3.2104 LTS 64
-Benchmark                           Mode  Cnt     Score    Error  Units (baseline)
-BigDecimals.testHugeToPlainString   avgt   15   188.687 ?  0.210  ns/op
-BigDecimals.testLargeToPlainString  avgt   15    36.547 ?  0.083  ns/op
-BigDecimals.testSmallToPlainString  avgt   15    34.270 ?  0.075  ns/op
-BigDecimals.testToPlainString       avgt   15  1661.588 ? 25.568  ns/op


+Benchmark                           Mode  Cnt     Score    Error  Units (optimized)
+BigDecimals.testHugeToPlainString   avgt   15   117.868 ?  0.390  ns/op (+60.08%)
+BigDecimals.testLargeToPlainString  avgt   15    15.903 ?  0.069  ns/op (+129.81%)
+BigDecimals.testSmallToPlainString  avgt   15    13.502 ?  0.051  ns/op (+153.81%)
+BigDecimals.testToPlainString       avgt   15  1556.691 ? 19.407  ns/op (+6.73%)

-Benchmark                                 Mode  Cnt     Score    Error  Units (baseline)
-BigDecimals.testHugeToEngineeringString   avgt   15   207.858 ?  4.166  ns/op
-BigDecimals.testLargeToEngineeringString  avgt   15    35.723 ?  2.772  ns/op
-BigDecimals.testSmallToEngineeringString  avgt   15    15.083 ?  0.052  ns/op
-BigDecimals.testToEngineeringString       avgt   15  1715.916 ? 37.066  ns/op

+Benchmark                                 Mode  Cnt     Score    Error  Units (optimized)
+BigDecimals.testHugeToEngineeringString   avgt   15   142.110 ?  0.987  ns/op (+46.26%)
+BigDecimals.testLargeToEngineeringString  avgt   15    12.509 ?  0.056  ns/op (+185.57%)
+BigDecimals.testSmallToEngineeringString  avgt   15    10.579 ?  0.066  ns/op (+42.57%)
+BigDecimals.testToEngineeringString       avgt   15  1646.783 ?  3.060  ns/op (+4.19%)

3.2. aliyun_ecs_c8a.xlarge

  • cpu : amd epc genoa (x64)
  • os : Alibaba Cloud Linux 3.2104 LTS 64
-Benchmark                                 Mode  Cnt     Score    Error  Units (baseline)
-BigDecimals.testHugeToEngineeringString   avgt   15   300.712 ?  8.526  ns/op
-BigDecimals.testLargeToEngineeringString  avgt   15    42.108 ?  0.149  ns/op
-BigDecimals.testSmallToEngineeringString  avgt   15    24.333 ?  0.076  ns/op
-BigDecimals.testToEngineeringString       avgt   15  2300.080 ? 12.640  ns/op


+Benchmark                                 Mode  Cnt     Score    Error  Units (optimized)
+BigDecimals.testHugeToEngineeringString   avgt   15   161.471 ?  1.303  ns/op (+86.23%)
+BigDecimals.testLargeToEngineeringString  avgt   15    24.847 ?  0.111  ns/op (+69.46%)
+BigDecimals.testSmallToEngineeringString  avgt   15    21.529 ?  0.088  ns/op (+13.02%)
+BigDecimals.testToEngineeringString       avgt   15  2016.281 ? 21.728  ns/op (+14.07%)


-Benchmark                           Mode  Cnt     Score    Error  Units (baseline)
-BigDecimals.testHugeToPlainString   avgt   15   277.892 ?  1.485  ns/op
-BigDecimals.testLargeToPlainString  avgt   15    42.669 ?  0.075  ns/op
-BigDecimals.testSmallToPlainString  avgt   15    38.928 ?  0.402  ns/op
-BigDecimals.testToPlainString       avgt   15  2245.593 ? 20.488  ns/op


+Benchmark                           Mode  Cnt     Score    Error  Units (optimized)
+BigDecimals.testHugeToPlainString   avgt   15   154.470 ?  7.259  ns/op (+79.90%)
+BigDecimals.testLargeToPlainString  avgt   15    29.892 ?  0.290  ns/op (+42.74%)
+BigDecimals.testSmallToPlainString  avgt   15    25.605 ?  0.109  ns/op (+52.03%)
+BigDecimals.testToPlainString       avgt   15  2027.892 ? 12.792  ns/op (+10.73%)

MaxBookPro M1 Pro

-Benchmark                                 Mode  Cnt     Score    Error  Units (baseline)
-BigDecimals.testHugeToEngineeringString   avgt   15   250.340 ?  4.911  ns/op
-BigDecimals.testLargeToEngineeringString  avgt   15    83.954 ?  7.603  ns/op
-BigDecimals.testSmallToEngineeringString  avgt   15    17.321 ?  0.083  ns/op
-BigDecimals.testToEngineeringString       avgt   15  1874.298 ? 71.820  ns/op


+Benchmark                                 Mode  Cnt     Score    Error  Units (optimized)
+BigDecimals.testHugeToEngineeringString   avgt   15   104.412 ? 17.752  ns/op (+139.76%)
+BigDecimals.testLargeToEngineeringString  avgt   15    14.617 ?  0.380  ns/op (+474.35%)
+BigDecimals.testSmallToEngineeringString  avgt   15    11.960 ?  0.021  ns/op (+44.82%)
+BigDecimals.testToEngineeringString       avgt   15  1622.669 ? 81.835  ns/op (+15.50%)


-Benchmark                           Mode  Cnt     Score    Error  Units (baseline)
-BigDecimals.testHugeToPlainString   avgt   15   188.559 ?  9.283  ns/op
-BigDecimals.testLargeToPlainString  avgt   15    22.894 ?  0.102  ns/op
-BigDecimals.testSmallToPlainString  avgt   15    24.029 ?  0.164  ns/op
-BigDecimals.testToPlainString       avgt   15  1858.483 ? 56.941  ns/op


+Benchmark                           Mode  Cnt     Score    Error  Units (optimized)
+BigDecimals.testHugeToPlainString   avgt   15    87.264 ?  0.677  ns/op (+116.07%)
+BigDecimals.testLargeToPlainString  avgt   15    16.665 ?  0.045  ns/op (+37.37%)
+BigDecimals.testSmallToPlainString  avgt   15    13.467 ?  0.091  ns/op (+78.42%)
+BigDecimals.testToPlainString       avgt   15  1593.317 ? 84.553  ns/op (+16.64%)

@wenshao
Copy link
Contributor Author

wenshao commented Sep 19, 2023

BigDecimal#toString is a common scenario. Can anyone help me continue the review? If someone thinks this PR changes too much, I can split it into multiple pull requests.

@cl4es @jddarcy

@wenshao
Copy link
Contributor Author

wenshao commented Oct 4, 2023

Seems like a step in the right direction w.r.t. code duplication. There's still a lot going on in this PR so it'll take some time to digest. Is there some way to split this into a series of enhancements for easier review? The jla.newStringLatin1NoRepl additions could be split out and done first - along with evidence that it helps in other places like UUID and HexFormat.

Thank you for taking the time to help with the review. I will split these changes into multiple PRs to make the changes in each PR smaller and easier to review. I have created a new PR #16006

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 2, 2023

@wenshao This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@openjdk
Copy link

openjdk bot commented Nov 13, 2023

@wenshao this pull request can not be integrated into master due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout optimization_for_decimal_to_string
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push

@openjdk openjdk bot added merge-conflict Pull request has merge conflict with target branch and removed rfr Pull request is ready for review labels Nov 13, 2023
@wenshao wenshao closed this Nov 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core-libs [email protected] merge-conflict Pull request has merge conflict with target branch

Development

Successfully merging this pull request may close these issues.

5 participants