RFR: 8334328: Reduce object allocation for FloatToDecimal and DoubleToDecimal [v13]
Raffaello Giulietti
rgiulietti at openjdk.org
Tue Jun 25 15:02:20 UTC 2024
On Mon, 17 Jun 2024 04:58:41 GMT, Shaojin Wen <duke at openjdk.org> wrote:
>> The current versions of FloatToDecimal and DoubleToDecimal allocate additional objects. Reducing these allocations can improve the performance of Float/Double.toString and AbstractStringBuilder's append(float/double).
>>
>> This patch is just a code refactoring to reduce object allocation, but does not change the Float/Double to decimal algorithm.
>>
>> The following code comments the allocated objects to be removed.
>>
>>
>> class FloatToDecimal {
>> public static String toString(float v) {
>> // allocate object FloatToDecimal
>> return new FloatToDecimal().toDecimalString(v);
>> }
>>
>> public static Appendable appendTo(float v, Appendable app)
>> throws IOException {
>> // allocate object FloatToDecimal
>> return new FloatToDecimal().appendDecimalTo(v, app);
>> }
>>
>> private Appendable appendDecimalTo(float v, Appendable app)
>> throws IOException {
>> switch (toDecimal(v)) {
>> case NON_SPECIAL:
>> // allocate object char[]
>> char[] chars = new char[index + 1];
>> for (int i = 0; i < chars.length; ++i) {
>> chars[i] = (char) bytes[i];
>> }
>> if (app instanceof StringBuilder builder) {
>> return builder.append(chars);
>> }
>> if (app instanceof StringBuffer buffer) {
>> return buffer.append(chars);
>> }
>> for (char c : chars) {
>> app.append(c);
>> }
>> return app;
>> // ...
>> }
>> }
>> }
>>
>> class DoubleToDecimal {
>> public static String toString(double v) {
>> // allocate object DoubleToDecimal
>> return new DoubleToDecimal(false).toDecimalString(v);
>> }
>>
>> public static Appendable appendTo(double v, Appendable app)
>> throws IOException {
>> // allocate object DoubleToDecimal
>> return new DoubleToDecimal(false).appendDecimalTo(v, app);
>> }
>>
>> private Appendable appendDecimalTo(double v, Appendable app)
>> throws IOException {
>> switch (toDecimal(v, null)) {
>> case NON_SPECIAL:
>> // allocate object char[]
>> char[] chars = new char[index + 1];
>> for (int i = 0; i < chars.length; ++i) {
>> chars[i] = (char) bytes[i];
>> }
>> ...
>
> Shaojin Wen has updated the pull request incrementally with one additional commit since the last revision:
>
> Utf16 case remove `append first utf16 char`
src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java line 43:
> 41: public final class DoubleToDecimal extends ToDecimal {
> 42: /**
> 43: * Use LATIN1 encoding to process the input byte[] str
Suggestion:
* Use LATIN1 encoding to process the in-out byte[] str
src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java line 49:
> 47:
> 48: /**
> 49: * Use UTF16 encoding to process the input byte[] str
Suggestion:
* Use UTF16 encoding to process the in-out byte[] str
src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java line 175:
> 173: */
> 174: public int putDecimal(byte[] str, int index, double v) {
> 175: assert index >= 0 && index + MAX_CHARS <= length(str) : "Trusted caller missed bounds check";
Suggestion:
assert 0 <= index && index <= length(str) - MAX_CHARS : "Trusted caller missed bounds check";
This avoids a potential overflow.
But if you want to enforce the check, you should probably avoid using `assert` since assertions might be disabled.
src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java line 236:
> 234: dk = -1;
> 235: }
> 236: return toDecimal(str, index, Q_MIN, t, dk, fd) - start;
I suggest restoring the original logic like so:
/* subnormal value */
return (t < C_TINY
? toDecimal(str, index, Q_MIN, 10 * t, -1, fd)
: toDecimal(str, index, Q_MIN, t, 0, fd)) - start;
src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java line 452:
> 450: if (l != 0) {
> 451: put8Digits(str, index, l);
> 452: index += 8;
As discussed in `ToDecimal`, all `put...` methods should return the updated `index`. Then this code can be rewritten as
Suggestion:
index = put8Digits(str, index, l);
so that the caller doesn't need to know how many characters were added.
src/java.base/share/classes/jdk/internal/math/DoubleToDecimal.java line 460:
> 458: putChar(str, index++, 'E');
> 459: if (e < 0) {
> 460: putChar(str, index++, '-');
By the same logic as suggested above for `put8Digits`
Suggestion:
index = putChar(str, index, '-');
and other places as well.
src/java.base/share/classes/jdk/internal/math/FloatToDecimal.java line 41:
> 39: * This class exposes a method to render a {@code float} as a string.
> 40: */
> 41: public final class FloatToDecimal extends ToDecimal {
The review of this class is the same as for `DoubleToDecimal`, so I won't repeat it here.
src/java.base/share/classes/jdk/internal/math/ToDecimal.java line 34:
> 32: import static java.lang.Math.multiplyHigh;
> 33:
> 34: sealed class ToDecimal permits DoubleToDecimal, FloatToDecimal {
I think this should better be
Suggestion:
abstract sealed class ToDecimal permits DoubleToDecimal, FloatToDecimal {
since we don't want/need to instantiate this class.
src/java.base/share/classes/jdk/internal/math/ToDecimal.java line 45:
> 43: static final int PLUS_INF = 0x300;
> 44: static final int MINUS_INF = 0x400;
> 45: static final int NAN = 0x500;
These constants could be like the original ones (0, 1, 2, ...).
This would make the switch over them a `tableswitch` rather than a `lookupswitch` in bytecode. I'm not sure whether this would improve machine code, however.
Of course, logic would need to be changed in some places (`<< 8`, `>>> 8`, etc.)
src/java.base/share/classes/jdk/internal/math/ToDecimal.java line 63:
> 61: }
> 62:
> 63: final void putChar(byte[] str, int index, int c) {
I think it would be better for all the `put...` methods here to return the updated `index`, hence replacing the return type from `void` to `int`.
This would make usages easier, as there's no need to remember at the call sites by how much the index must be incremented after the usage.
src/java.base/share/classes/jdk/internal/math/ToDecimal.java line 71:
> 69: }
> 70:
> 71: final void putCharsAt(byte[] str, int index, int c1, int c2) {
This method is used to put at least one digit. This means that the caller has to remember to add a '0' explicitly, which is questionable.
I suggest removing this method and invoke the correct combination of `putChar` and `putDigit` for more readable code.
src/java.base/share/classes/jdk/internal/math/ToDecimal.java line 165:
> 163: }
> 164:
> 165: int length(byte[] str) {
Suggestion:
final int length(byte[] str) {
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652975219
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652975321
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652972922
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652978056
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652984938
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652987069
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652991581
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652933181
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652993152
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652993281
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652993363
PR Review Comment: https://git.openjdk.org/jdk/pull/19730#discussion_r1652993591
More information about the core-libs-dev
mailing list