Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Back port] 8310929: Optimization for integer/long toString #682

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 10 additions & 38 deletions src/java.base/share/classes/java/lang/Integer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
25 changes: 11 additions & 14 deletions src/java.base/share/classes/java/lang/Long.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -528,31 +529,27 @@ 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
int q2;
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) {
Expand Down
48 changes: 25 additions & 23 deletions src/java.base/share/classes/java/lang/StringUTF16.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -1432,38 +1432,40 @@ 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
int q2;
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) {
putChar(buf, --charPos, '-');
}
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) {
Expand Down
90 changes: 90 additions & 0 deletions src/java.base/share/classes/jdk/internal/util/DecimalDigits.java
Original file line number Diff line number Diff line change
@@ -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:<p>
* <pre>
* 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
* </pre>
*/
@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);
}
}
Loading