8310929: Optimization for Integer.toString

Reviewed-by: redestad, rriggs
This commit is contained in:
shaojin.wensj 2023-09-08 02:13:52 +00:00 committed by Yi Yang
parent 111ecdbaf5
commit 4b43c25fe3
8 changed files with 215 additions and 172 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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
@ -832,7 +832,7 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence
int spaceNeeded = count + Integer.stringSize(i);
ensureCapacityInternal(spaceNeeded);
if (isLatin1()) {
Integer.getChars(i, spaceNeeded, value);
StringLatin1.getChars(i, spaceNeeded, value);
} else {
StringUTF16.getChars(i, count, spaceNeeded, value);
}
@ -857,7 +857,7 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence
int spaceNeeded = count + Long.stringSize(l);
ensureCapacityInternal(spaceNeeded);
if (isLatin1()) {
Long.getChars(l, spaceNeeded, value);
StringLatin1.getChars(l, spaceNeeded, value);
} else {
StringUTF16.getChars(l, count, spaceNeeded, value);
}

View File

@ -414,33 +414,6 @@ public final class Integer extends Number
} while (charPos > 0);
}
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
@ -456,7 +429,7 @@ public final class Integer extends Number
int size = stringSize(i);
if (COMPACT_STRINGS) {
byte[] buf = new byte[size];
getChars(i, size, buf);
StringLatin1.getChars(i, size, buf);
return new String(buf, LATIN1);
} else {
byte[] buf = new byte[size * 2];
@ -483,53 +456,6 @@ public final class Integer extends Number
return Long.toString(toUnsignedLong(i));
}
/**
* Places characters representing the integer i into the
* character array buf. The characters are placed into
* the buffer backwards starting with the least significant
* digit at the specified index (exclusive), and working
* backwards from there.
*
* @implNote This method converts positive inputs into negative
* values, to cover the Integer.MIN_VALUE case. Converting otherwise
* (negative to positive) will expose -Integer.MIN_VALUE that overflows
* integer.
*
* @param i value to convert
* @param index next index, after the least significant digit
* @param buf target buffer, Latin1-encoded
* @return index of the most significant digit or minus sign, if present
*/
static int getChars(int i, int index, byte[] buf) {
int q, r;
int charPos = index;
boolean negative = i < 0;
if (!negative) {
i = -i;
}
// Generate two digits per iteration
while (i <= -100) {
q = i / 100;
r = (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.
buf[--charPos] = DigitOnes[-i];
if (i < -9) {
buf[--charPos] = DigitTens[-i];
}
if (negative) {
buf[--charPos] = (byte)'-';
}
return charPos;
}
/**
* Returns the string representation size for a given int value.
*

View File

@ -459,7 +459,7 @@ public final class Long extends Number
int size = stringSize(i);
if (COMPACT_STRINGS) {
byte[] buf = new byte[size];
getChars(i, size, buf);
StringLatin1.getChars(i, size, buf);
return new String(buf, LATIN1);
} else {
byte[] buf = new byte[size * 2];
@ -486,65 +486,6 @@ public final class Long extends Number
return toUnsignedString(i, 10);
}
/**
* Places characters representing the long i into the
* character array buf. The characters are placed into
* the buffer backwards starting with the least significant
* digit at the specified index (exclusive), and working
* backwards from there.
*
* @implNote This method converts positive inputs into negative
* values, to cover the Long.MIN_VALUE case. Converting otherwise
* (negative to positive) will expose -Long.MIN_VALUE that overflows
* long.
*
* @param i value to convert
* @param index next index, after the least significant digit
* @param buf target buffer, Latin1-encoded
* @return index of the most significant digit or minus sign, if present
*/
static int getChars(long i, int index, byte[] buf) {
long q;
int r;
int charPos = index;
boolean negative = (i < 0);
if (!negative) {
i = -i;
}
// 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);
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;
i2 = q2;
buf[--charPos] = Integer.DigitOnes[r];
buf[--charPos] = Integer.DigitTens[r];
}
// We know there are at most two digits left at this point.
buf[--charPos] = Integer.DigitOnes[-i2];
if (i2 < -9) {
buf[--charPos] = Integer.DigitTens[-i2];
}
if (negative) {
buf[--charPos] = (byte)'-';
}
return charPos;
}
/**
* Returns the string representation size for a given long value.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -250,7 +250,7 @@ final class StringConcatHelper {
*/
private static long prepend(long indexCoder, byte[] buf, int value) {
if (indexCoder < UTF16) {
return Integer.getChars(value, (int)indexCoder, buf);
return StringLatin1.getChars(value, (int)indexCoder, buf);
} else {
return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16;
}
@ -285,7 +285,7 @@ final class StringConcatHelper {
*/
private static long prepend(long indexCoder, byte[] buf, long value) {
if (indexCoder < UTF16) {
return Long.getChars(value, (int)indexCoder, buf);
return StringLatin1.getChars(value, (int)indexCoder, buf);
} else {
return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16;
}

View File

@ -33,7 +33,9 @@ import java.util.function.IntConsumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.util.ArraysSupport;
import jdk.internal.util.ByteArrayLittleEndian;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
import static java.lang.String.LATIN1;
import static java.lang.String.UTF16;
@ -42,6 +44,40 @@ import static java.lang.String.checkOffset;
final class StringLatin1 {
/**
* 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
static final short[] PACKED_DIGITS = new short[] {
0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930,
0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931,
0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932,
0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933,
0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934,
0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935,
0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936,
0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937,
0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938,
0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939
};
public static char charAt(byte[] value, int index) {
checkIndex(index, value.length);
return (char)(value[index] & 0xff);
@ -79,6 +115,115 @@ final class StringLatin1 {
return ret;
}
/**
* Places characters representing the integer i into the
* character array buf. The characters are placed into
* the buffer backwards starting with the least significant
* digit at the specified index (exclusive), and working
* backwards from there.
*
* @implNote This method converts positive inputs into negative
* values, to cover the Integer.MIN_VALUE case. Converting otherwise
* (negative to positive) will expose -Integer.MIN_VALUE that overflows
* integer.
*
* @param i value to convert
* @param index next index, after the least significant digit
* @param buf target buffer, Latin1-encoded
* @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;
boolean negative = i < 0;
if (!negative) {
i = -i;
}
// Generate two digits per iteration
while (i <= -100) {
q = i / 100;
r = (q * 100) - i;
i = q;
charPos -= 2;
ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[r]);
}
// We know there are at most two digits left at this point.
if (i < -9) {
charPos -= 2;
ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[-i]);
} else {
buf[--charPos] = (byte)('0' - i);
}
if (negative) {
buf[--charPos] = (byte)'-';
}
return charPos;
}
/**
* Places characters representing the long i into the
* character array buf. The characters are placed into
* the buffer backwards starting with the least significant
* digit at the specified index (exclusive), and working
* backwards from there.
*
* @implNote This method converts positive inputs into negative
* values, to cover the Long.MIN_VALUE case. Converting otherwise
* (negative to positive) will expose -Long.MIN_VALUE that overflows
* long.
*
* @param i value to convert
* @param index next index, after the least significant digit
* @param buf target buffer, Latin1-encoded
* @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 charPos = index;
boolean negative = (i < 0);
if (!negative) {
i = -i;
}
// Get 2 digits/iteration using longs until quotient fits into an int
while (i <= Integer.MIN_VALUE) {
q = i / 100;
charPos -= 2;
ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[(int)((q * 100) - i)]);
i = q;
}
// Get 2 digits/iteration using ints
int q2;
int i2 = (int)i;
while (i2 <= -100) {
q2 = i2 / 100;
charPos -= 2;
ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[(q2 * 100) - i2]);
i2 = q2;
}
// We know there are at most two digits left at this point.
if (i2 < -9) {
charPos -= 2;
ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[-i2]);
} else {
buf[--charPos] = (byte)('0' - i2);
}
if (negative) {
buf[--charPos] = (byte)'-';
}
return charPos;
}
public static void getChars(byte[] value, int srcBegin, int srcEnd, char[] dst, int dstBegin) {
inflate(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

View File

@ -33,6 +33,7 @@ import java.util.function.IntConsumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.util.ArraysSupport;
import jdk.internal.util.ByteArrayLittleEndian;
import jdk.internal.vm.annotation.DontInline;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
@ -1519,7 +1520,7 @@ final class StringUTF16 {
// been done by the caller.
/**
* This is a variant of {@link Integer#getChars(int, int, byte[])}, but for
* This is a variant of {@link StringLatin1#getChars(int, int, byte[])}, but for
* UTF-16 coder.
*
* @param i value to convert
@ -1528,6 +1529,7 @@ final class StringUTF16 {
* @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;
@ -1541,14 +1543,23 @@ final class StringUTF16 {
q = i / 100;
r = (q * 100) - i;
i = q;
putChar(buf, --charPos, Integer.DigitOnes[r]);
putChar(buf, --charPos, Integer.DigitTens[r]);
int packed = (int) StringLatin1.PACKED_DIGITS[r];
int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF);
charPos -= 2;
ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated);
}
// We know there are at most two digits left at this point.
putChar(buf, --charPos, Integer.DigitOnes[-i]);
if (i < -9) {
putChar(buf, --charPos, Integer.DigitTens[-i]);
int packed = (int) StringLatin1.PACKED_DIGITS[-i];
int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF);
charPos -= 2;
ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated);
} else {
putChar(buf, --charPos, '0' - i);
}
if (negative) {
@ -1558,7 +1569,7 @@ final class StringUTF16 {
}
/**
* This is a variant of {@link Long#getChars(long, int, byte[])}, but for
* This is a variant of {@link StringLatin1#getChars(long, int, byte[])}, but for
* UTF-16 coder.
*
* @param i value to convert
@ -1567,8 +1578,8 @@ final class StringUTF16 {
* @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);
@ -1579,10 +1590,13 @@ final class StringUTF16 {
// 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);
int packed = (int) StringLatin1.PACKED_DIGITS[(int)((q * 100) - i)];
int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF);
charPos -= 2;
ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated);
i = q;
putChar(buf, --charPos, Integer.DigitOnes[r]);
putChar(buf, --charPos, Integer.DigitTens[r]);
}
// Get 2 digits/iteration using ints
@ -1590,16 +1604,25 @@ final class StringUTF16 {
int i2 = (int)i;
while (i2 <= -100) {
q2 = i2 / 100;
r = (q2 * 100) - i2;
int packed = (int) StringLatin1.PACKED_DIGITS[(q2 * 100) - i2];
int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF);
charPos -= 2;
ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated);
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.
putChar(buf, --charPos, Integer.DigitOnes[-i2]);
if (i2 < -9) {
putChar(buf, --charPos, Integer.DigitTens[-i2]);
charPos -= 2;
int packed = (int) StringLatin1.PACKED_DIGITS[-i2];
int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF);
ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated);
} else {
putChar(buf, --charPos, '0' - i2);
}
if (negative) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -23,13 +23,20 @@
/*
* @test
* @bug 8136500
* @summary Test Integer.toString method
* @bug 8136500 8310929
* @summary Test Integer.toString method for both compact and non-compact strings
* @run junit/othervm -XX:+CompactStrings ToString
* @run junit/othervm -XX:-CompactStrings ToString
*/
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ToString {
public static void main(String[] args) throws Exception {
@Test
public void testBase10() {
test("-2147483648", Integer.MIN_VALUE);
test("2147483647", Integer.MAX_VALUE);
test("0", 0);
@ -77,9 +84,6 @@ public class ToString {
}
private static void test(String expected, int value) {
String actual = Integer.toString(value);
if (!expected.equals(actual)) {
throw new RuntimeException("Expected " + expected + ", but got " + actual);
}
assertEquals(expected, Integer.toString(value));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -23,13 +23,20 @@
/*
* @test
* @bug 8136500
* @summary Test Long.toString method
* @bug 8136500 8310929
* @summary Test Long.toString method for both compact and non-compact strings
* @run junit/othervm -XX:+CompactStrings ToString
* @run junit/othervm -XX:-CompactStrings ToString
*/
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ToString {
public static void main(String[] args) throws Exception {
@Test
public void testBase10() {
test("-9223372036854775808", Long.MIN_VALUE);
test("9223372036854775807", Long.MAX_VALUE);
test("0", 0);
@ -77,9 +84,6 @@ public class ToString {
}
private static void test(String expected, long value) {
String actual = Long.toString(value);
if (!expected.equals(actual)) {
throw new RuntimeException("Expected " + expected + ", but got " + actual);
}
assertEquals(expected, Long.toString(value));
}
}