From 1d420263788ac5f47768d42e1d0bf5033dde5750 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Thu, 5 Oct 2023 10:43:06 +0800 Subject: [PATCH] back port from openjdk, optimization for integer/long toString --- .../share/classes/java/lang/Integer.java | 48 +++------- .../share/classes/java/lang/Long.java | 25 +++--- .../share/classes/java/lang/StringUTF16.java | 48 +++++----- .../jdk/internal/util/DecimalDigits.java | 90 +++++++++++++++++++ 4 files changed, 136 insertions(+), 75 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/util/DecimalDigits.java diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 43cd0f3e6a4..39143ac74c4 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -29,6 +29,7 @@ import java.util.Objects; import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.misc.VM; +import jdk.internal.util.DecimalDigits; import static java.lang.String.COMPACT_STRINGS; import static java.lang.String.LATIN1; @@ -396,33 +397,6 @@ private static void formatUnsignedIntUTF16(int val, int shift, byte[] buf, int o } while (charPos > offset); } - static final byte[] DigitTens = { - '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', - '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', - '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', - '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', - '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', - '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', - '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', - '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', - '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', - } ; - - static final byte[] DigitOnes = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - } ; - - /** * Returns a {@code String} object representing the * specified integer. The argument is converted to signed decimal @@ -483,7 +457,8 @@ public static String toUnsignedString(int i) { * @return index of the most significant digit or minus sign, if present */ static int getChars(int i, int index, byte[] buf) { - int q, r; + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q; int charPos = index; boolean negative = i < 0; @@ -494,20 +469,17 @@ static int getChars(int i, int index, byte[] buf) { // Generate two digits per iteration while (i <= -100) { q = i / 100; - r = (q * 100) - i; + charPos -= 2; + DecimalDigits.putPair(buf, charPos, (q * 100) - i); i = q; - buf[--charPos] = DigitOnes[r]; - buf[--charPos] = DigitTens[r]; } // We know there are at most two digits left at this point. - q = i / 10; - r = (q * 10) - i; - buf[--charPos] = (byte)('0' + r); - - // Whatever left is the remaining digit. - if (q < 0) { - buf[--charPos] = (byte)('0' - q); + if (i < -9) { + charPos -= 2; + DecimalDigits.putPair(buf, charPos, -i); + } else { + buf[--charPos] = (byte)('0' - i); } if (negative) { diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 489417ac5fa..4f4a0234a31 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -29,6 +29,7 @@ import java.math.*; import java.util.Objects; import jdk.internal.HotSpotIntrinsicCandidate; +import jdk.internal.util.DecimalDigits; import static java.lang.String.COMPACT_STRINGS; import static java.lang.String.LATIN1; @@ -516,8 +517,8 @@ public static String toUnsignedString(long i) { * @return index of the most significant digit or minus sign, if present */ static int getChars(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. long q; - int r; int charPos = index; boolean negative = (i < 0); @@ -528,10 +529,9 @@ static int getChars(long i, int index, byte[] buf) { // Get 2 digits/iteration using longs until quotient fits into an int while (i <= Integer.MIN_VALUE) { q = i / 100; - r = (int)((q * 100) - i); + charPos -= 2; + DecimalDigits.putPair(buf, charPos, (int)((q * 100) - i)); i = q; - buf[--charPos] = Integer.DigitOnes[r]; - buf[--charPos] = Integer.DigitTens[r]; } // Get 2 digits/iteration using ints @@ -539,20 +539,17 @@ static int getChars(long i, int index, byte[] buf) { int i2 = (int)i; while (i2 <= -100) { q2 = i2 / 100; - r = (q2 * 100) - i2; + charPos -= 2; + DecimalDigits.putPair(buf, charPos, (q2 * 100) - i2); i2 = q2; - buf[--charPos] = Integer.DigitOnes[r]; - buf[--charPos] = Integer.DigitTens[r]; } // We know there are at most two digits left at this point. - q2 = i2 / 10; - r = (q2 * 10) - i2; - buf[--charPos] = (byte)('0' + r); - - // Whatever left is the remaining digit. - if (q2 < 0) { - buf[--charPos] = (byte)('0' - q2); + if (i2 < -9) { + charPos -= 2; + DecimalDigits.putPair(buf, charPos, -i2); + } else { + buf[--charPos] = (byte)('0' - i2); } if (negative) { diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index 331b518123a..1e0360e113c 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -33,6 +33,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.HotSpotIntrinsicCandidate; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.DontInline; @@ -1377,6 +1378,7 @@ public static int lastIndexOfLatin1(byte[] src, int srcCount, * @return index of the most significant digit or minus sign, if present */ static int getChars(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. int q, r; int charPos = index; @@ -1390,18 +1392,16 @@ static int getChars(int i, int index, byte[] buf) { q = i / 100; r = (q * 100) - i; i = q; - putChar(buf, --charPos, Integer.DigitOnes[r]); - putChar(buf, --charPos, Integer.DigitTens[r]); + charPos -= 2; + putPair(buf, charPos, r); } // We know there are at most two digits left at this point. - q = i / 10; - r = (q * 10) - i; - putChar(buf, --charPos, '0' + r); - - // Whatever left is the remaining digit. - if (q < 0) { - putChar(buf, --charPos, '0' - q); + if (i < -9) { + charPos -= 2; + putPair(buf, charPos, -i); + } else { + putChar(buf, --charPos, '0' - i); } if (negative) { @@ -1420,8 +1420,8 @@ static int getChars(int i, int index, byte[] buf) { * @return index of the most significant digit or minus sign, if present */ static int getChars(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. long q; - int r; int charPos = index; boolean negative = (i < 0); @@ -1432,10 +1432,9 @@ static int getChars(long i, int index, byte[] buf) { // Get 2 digits/iteration using longs until quotient fits into an int while (i <= Integer.MIN_VALUE) { q = i / 100; - r = (int)((q * 100) - i); + charPos -= 2; + putPair(buf, charPos, (int)((q * 100) - i)); i = q; - putChar(buf, --charPos, Integer.DigitOnes[r]); - putChar(buf, --charPos, Integer.DigitTens[r]); } // Get 2 digits/iteration using ints @@ -1443,20 +1442,17 @@ static int getChars(long i, int index, byte[] buf) { int i2 = (int)i; while (i2 <= -100) { q2 = i2 / 100; - r = (q2 * 100) - i2; + charPos -= 2; + putPair(buf, charPos, (q2 * 100) - i2); i2 = q2; - putChar(buf, --charPos, Integer.DigitOnes[r]); - putChar(buf, --charPos, Integer.DigitTens[r]); } // We know there are at most two digits left at this point. - q2 = i2 / 10; - r = (q2 * 10) - i2; - putChar(buf, --charPos, '0' + r); - - // Whatever left is the remaining digit. - if (q2 < 0) { - putChar(buf, --charPos, '0' - q2); + if (i2 < -9) { + charPos -= 2; + putPair(buf, charPos, -i2); + } else { + putChar(buf, --charPos, '0' - i2); } if (negative) { @@ -1464,6 +1460,12 @@ static int getChars(long i, int index, byte[] buf) { } return charPos; } + + static void putPair(byte[] buf, int charPos, int v) { + int packed = (int) DecimalDigits.digitPair(v); + putChar(buf, charPos, packed & 0xFF); + putChar(buf, charPos + 1, packed >> 8); + } // End of trusted methods. public static void checkIndex(int off, byte[] val) { diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java new file mode 100644 index 00000000000..d072cae949a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.util; + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.Stable; + +/** + * Digits class for decimal digits. + * + */ +public final class DecimalDigits { + private static Unsafe UNSAFE = Unsafe.getUnsafe(); + + /** + * Each element of the array represents the packaging of two ascii characters based on little endian:

+ *

+     *      00 -> '0' | ('0' << 8) -> 0x3030
+     *      01 -> '1' | ('0' << 8) -> 0x3130
+     *      02 -> '2' | ('0' << 8) -> 0x3230
+     *
+     *     ...
+     *
+     *      10 -> '0' | ('1' << 8) -> 0x3031
+     *      11 -> '1' | ('1' << 8) -> 0x3131
+     *      12 -> '2' | ('1' << 8) -> 0x3231
+     *
+     *     ...
+     *
+     *      97 -> '7' | ('9' << 8) -> 0x3739
+     *      98 -> '8' | ('9' << 8) -> 0x3839
+     *      99 -> '9' | ('9' << 8) -> 0x3939
+     * 
+ */ + @Stable + private static final short[] DIGITS; + + static { + short[] digits = new short[10 * 10]; + + for (int i = 0; i < 10; i++) { + short hi = (short) (i + '0'); + for (int j = 0; j < 10; j++) { + short lo = (short) ((j + '0') << 8); + digits[i * 10 + j] = (short) (hi | lo); + } + } + DIGITS = digits; + } + + /** + * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian + * @param i value to convert + * @return a short encoding a pair of ASCII-encoded digit characters + */ + public static short digitPair(int i) { + return DIGITS[i]; + } + + public static void putPair(byte[] buf, int charPos, int value) { + UNSAFE.putShortUnaligned( + buf, + Unsafe.ARRAY_BYTE_BASE_OFFSET + charPos, + DIGITS[value], + false); + } +}