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); + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/Integers.java b/test/micro/org/openjdk/bench/java/lang/Integers.java new file mode 100644 index 00000000000..43ceb5d18d2 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/Integers.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2014, 2022, 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. + * + * 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 org.openjdk.bench.java.lang; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Test various java.lang.Integer operations + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Thread) +@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Fork(3) +public class Integers { + + @Param("500") + private int size; + + private int bound; + private String[] strings; + private int[] intsTiny; + private int[] intsSmall; + private int[] intsBig; + private int[] res; + + @Setup + public void setup() { + Random r = new Random(0); + bound = 50; + strings = new String[size]; + intsTiny = new int[size]; + intsSmall = new int[size]; + intsBig = new int[size]; + res = new int[size]; + for (int i = 0; i < size; i++) { + strings[i] = "" + (r.nextInt(10000) - (5000)); + intsTiny[i] = r.nextInt(99); + intsSmall[i] = 100 * i + i + 103; + intsBig[i] = ((100 * i + i) << 24) + 4543 + i * 4; + } + } + + @Benchmark + public void parseInt(Blackhole bh) { + for (String s : strings) { + bh.consume(Integer.parseInt(s)); + } + } + + @Benchmark + public void decode(Blackhole bh) { + for (String s : strings) { + bh.consume(Integer.decode(s)); + } + } + + /** Performs toString on small values, just a couple of digits. */ + @Benchmark + public void toStringSmall(Blackhole bh) { + for (int i : intsSmall) { + bh.consume(Integer.toString(i)); + } + } + + /** Performs toString on very small values, just one or two digits. */ + @Benchmark + public void toStringTiny(Blackhole bh) { + for (int i : intsTiny) { + bh.consume(Integer.toString(i)); + } + } + + /** Performs toString on large values, roughly 10 digits. */ + @Benchmark + public void toStringBig(Blackhole bh) { + for (int i : intsBig) { + bh.consume(Integer.toString(i)); + } + } + + /** Performs expand on small values */ + @Benchmark + public void expand(Blackhole bh) { + for (int i : intsSmall) { + bh.consume(Integer.expand(i, 0xFF00F0F0)); + } + } + + /** Performs compress on large values */ + @Benchmark + public void compress(Blackhole bh) { + for (int i : intsBig) { + bh.consume(Integer.compress(i, 0x000F0F1F)); + } + } + + @Benchmark + public void shiftRight(Blackhole bh) { + for (int i = 0; i < size; i++) { + bh.consume(intsBig[i] >> intsSmall[i]); + } + } + + @Benchmark + public void shiftURight(Blackhole bh) { + for (int i = 0; i < size; i++) { + bh.consume(intsBig[i] >>> intsSmall[i]); + } + } + + @Benchmark + public void shiftLeft(Blackhole bh) { + for (int i = 0; i < size; i++) { + bh.consume(intsBig[i] << intsSmall[i]); + } + } + + @Benchmark + public void compareUnsignedIndirect(Blackhole bh) { + for (int i = 0; i < size; i++) { + int r = (Integer.compareUnsigned(intsSmall[i], bound - 16) < 0) ? 1 : 0; + bh.consume(r); + } + } + + @Benchmark + public void compareUnsignedDirect(Blackhole bh) { + for (int i = 0; i < size; i++) { + int r = Integer.compareUnsigned(intsSmall[i], bound - 16); + bh.consume(r); + } + } + + @Benchmark + public void reverseBytes() { + for (int i = 0; i < size; i++) { + res[i] = Integer.reverseBytes(intsSmall[i]); + } + } + + @Benchmark + public void reverse() { + for (int i = 0; i < size; i++) { + res[i] = Integer.reverse(intsSmall[i]); + } + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/Longs.java b/test/micro/org/openjdk/bench/java/lang/Longs.java new file mode 100644 index 00000000000..765d00e9fb9 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/Longs.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2014, 2022, 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. + * + * 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 org.openjdk.bench.java.lang; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Thread) +@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Fork(3) +public class Longs { + + @Param("500") + private int size; + + private long bound; + private long[] res; + private String[] strings; + private long[] longArraySmall; + private long[] longArrayBig; + + @Setup + public void setup() { + var random = ThreadLocalRandom.current(); + bound = 20000L; + strings = new String[size]; + res = new long[size]; + longArraySmall = new long[size]; + longArrayBig = new long[size]; + for (int i = 0; i < size; i++) { + strings[i] = "" + (random.nextLong(10000) - 5000); + longArraySmall[i] = 100L * i + i + 103L; + longArrayBig[i] = ((100L * i + i) << 32) + 4543 + i * 4L; + } + } + + /** Performs toString on small values, just a couple of digits. */ + @Benchmark + public void toStringSmall(Blackhole bh) { + for (long value : longArraySmall) { + bh.consume(Long.toString(value)); + } + } + + @Benchmark + public void decode(Blackhole bh) { + for (String s : strings) { + bh.consume(Long.decode(s)); + } + } + + /** Performs toString on large values, around 10 digits. */ + @Benchmark + public void toStringBig(Blackhole bh) { + for (long value : longArrayBig) { + bh.consume(Long.toString(value)); + } + } + + /** Performs expand on small values */ + @Benchmark + public void expand(Blackhole bh) { + for (long i : longArraySmall) { + bh.consume(Long.expand(i, 0xFF00F0F0F0000000L)); + } + } + + /** Performs compress on large values */ + @Benchmark + public void compress(Blackhole bh) { + for (long i : longArrayBig) { + bh.consume(Long.compress(i, 0x000000000F0F0F1FL)); + } + } + + /* + * Have them public to avoid total unrolling + */ + public int innerLoops = 1500; + + @Benchmark + public long repetitiveSubtraction() { + long x = 127, dx = 0; + + for (int i = 0; i < innerLoops; i++) { + x -= dx; + dx = (dx - x); + } + return x; + } + + @Benchmark + public void shiftRight(Blackhole bh) { + for (int i = 0; i < size; i++) { + bh.consume(longArrayBig[i] >> longArraySmall[i]); + } + } + + @Benchmark + public void shiftURight(Blackhole bh) { + for (int i = 0; i < size; i++) { + bh.consume(longArrayBig[i] >>> longArraySmall[i]); + } + } + + @Benchmark + public void shiftLeft(Blackhole bh) { + for (int i = 0; i < size; i++) { + bh.consume(longArrayBig[i] << longArraySmall[i]); + } + } + + @Benchmark + public void compareUnsignedIndirect(Blackhole bh) { + for (int i = 0; i < size; i++) { + int r = (Long.compareUnsigned(longArraySmall[i], bound - 16) < 0) ? 1 : 0; + bh.consume(r); + } + } + + @Benchmark + public void compareUnsignedDirect(Blackhole bh) { + for (int i = 0; i < size; i++) { + int r = Long.compareUnsigned(longArraySmall[i], bound - 16); + bh.consume(r); + } + } + + @Benchmark + public void reverseBytes() { + for (int i = 0; i < size; i++) { + res[i] = Long.reverseBytes(longArraySmall[i]); + } + } + + @Benchmark + public void reverse() { + for (int i = 0; i < size; i++) { + res[i] = Long.reverse(longArraySmall[i]); + } + } +}