diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 41487a469b6a0..67c9dd29a8473 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -28,6 +28,7 @@ import jdk.internal.misc.CDS; import jdk.internal.misc.VM; import jdk.internal.util.DecimalDigits; +import jdk.internal.util.HexDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -287,7 +288,24 @@ public static String toUnsignedString(int i, int radix) { * @since 1.0.2 */ public static String toHexString(int i) { - return toUnsignedString0(i, 4); + int mag = Integer.SIZE - Integer.numberOfLeadingZeros(i); + int len = Math.max(((mag + 3) >> 2), 1); + long x = HexDigits.hex8(i); + if (COMPACT_STRINGS) { + byte[] chars = new byte[len]; + do { + chars[--len] = (byte) x; + x >>>= 8; + } while (len > 0); + return new String(chars, LATIN1); + } else { + byte[] chars = new byte[len << 1]; + do { + StringUTF16.putChar(chars, --len, (byte) x); + x >>>= 8; + } while (len > 0); + return new String(chars, UTF16); + } } /** diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 2fb2d18a78c80..87177cdef41c7 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -34,7 +34,9 @@ import java.util.Optional; import jdk.internal.misc.CDS; +import jdk.internal.misc.Unsafe; import jdk.internal.util.DecimalDigits; +import jdk.internal.util.HexDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -43,6 +45,7 @@ import static java.lang.String.COMPACT_STRINGS; import static java.lang.String.LATIN1; import static java.lang.String.UTF16; +import static jdk.internal.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; /** * The {@code Long} class is the {@linkplain @@ -311,7 +314,38 @@ private static BigInteger toUnsignedBigInteger(long i) { * @since 1.0.2 */ public static String toHexString(long i) { - return toUnsignedString0(i, 4); + int mag = Long.SIZE - Long.numberOfLeadingZeros(i); + int len = Math.max(((mag + 3) >> 2), 1); + long x = HexDigits.hex8(i); + if (COMPACT_STRINGS) { + byte[] chars = new byte[len]; + if (len > 8) { + len -= 8; + Unsafe.getUnsafe().putLongUnaligned(chars, ARRAY_BYTE_BASE_OFFSET + len, x, true); + x = HexDigits.hex8(i >>> 32); + } + do { + chars[--len] = (byte) x; + x >>>= 8; + } while (len > 0); + return new String(chars, LATIN1); + } else { + byte[] chars = new byte[len << 1]; + byte b; + if (len > 8) { + for (int j = 0; j < 8; j++) { + b = (byte) x; + StringUTF16.putChar(chars, --len, b); + x >>>= 8; + } + x = HexDigits.hex8(i >>> 32); + } + do { + StringUTF16.putChar(chars, --len, (byte) x); + x >>>= 8; + } while (len > 0); + return new String(chars, UTF16); + } } /** diff --git a/src/java.base/share/classes/java/util/UUID.java b/src/java.base/share/classes/java/util/UUID.java index 5961fce9cb23c..8fb07cba04341 100644 --- a/src/java.base/share/classes/java/util/UUID.java +++ b/src/java.base/share/classes/java/util/UUID.java @@ -32,6 +32,7 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.ByteArrayLittleEndian; +import jdk.internal.util.HexDigits; /** * A class that represents an immutable universally unique identifier (UUID). @@ -468,16 +469,16 @@ public String toString() { buf[23] = '-'; // Although the UUID byte ordering is defined to be big-endian, ByteArrayLittleEndian is used here to optimize - // for the most common architectures. hex8 reverses the order internally. - ByteArrayLittleEndian.setLong(buf, 0, hex8(mostSigBits >>> 32)); - long x0 = hex8(mostSigBits); + // for the most common architectures. + ByteArrayLittleEndian.setLong(buf, 0, Long.reverseBytes(HexDigits.hex8(mostSigBits >>> 32))); + long x0 = Long.reverseBytes(HexDigits.hex8(mostSigBits)); ByteArrayLittleEndian.setInt(buf, 9, (int) x0); ByteArrayLittleEndian.setInt(buf, 14, (int) (x0 >>> 32)); - long x1 = hex8(leastSigBits >>> 32); + long x1 = Long.reverseBytes(HexDigits.hex8(leastSigBits >>> 32)); ByteArrayLittleEndian.setInt(buf, 19, (int) (x1)); ByteArrayLittleEndian.setInt(buf, 24, (int) (x1 >>> 32)); - ByteArrayLittleEndian.setLong(buf, 28, hex8(leastSigBits)); + ByteArrayLittleEndian.setLong(buf, 28, Long.reverseBytes(HexDigits.hex8(leastSigBits))); try { return jla.uncheckedNewStringNoRepl(buf, StandardCharsets.ISO_8859_1); @@ -486,83 +487,6 @@ public String toString() { } } - /** - * Efficiently converts 8 hexadecimal digits to their ASCII representation using SIMD-style vector operations. - * This method processes multiple digits in parallel by treating a long value as eight 8-bit lanes, - * achieving significantly better performance compared to traditional loop-based conversion. - * - *
The conversion algorithm works as follows: - *
- * 1. Input expansion: Each 4-bit hex digit is expanded to 8 bits - * 2. Vector processing: - * - Add 6 to each digit: triggers carry flag for a-f digits - * - Mask with 0x10 pattern to isolate carry flags - * - Calculate ASCII adjustment: (carry << 1) + (carry >> 1) - (carry >> 4) - * - Add ASCII '0' base (0x30) and original value - * 3. Byte order adjustment for final output - *- * - *
Performance characteristics: - *
ASCII conversion mapping: - *
- * Input: 0xABCDEF01
- * Output: 3130666564636261 ('1','0','f','e','d','c','b','a' in ASCII)
- *
- *
- * @see Long#reverseBytes(long)
- */
- private static long hex8(long i) {
- // Expand each 4-bit group into 8 bits, spreading them out in the long value: 0xAABBCCDD -> 0xA0A0B0B0C0C0D0D
- i = Long.expand(i, 0x0F0F_0F0F_0F0F_0F0FL);
-
- /*
- * This method efficiently converts 8 hexadecimal digits simultaneously using vector operations
- * The algorithm works as follows:
- *
- * For input values 0-15:
- * - For digits 0-9: converts to ASCII '0'-'9' (0x30-0x39)
- * - For digits 10-15: converts to ASCII 'a'-'f' (0x61-0x66)
- *
- * The conversion process:
- * 1. Add 6 to each 4-bit group: i + 0x0606_0606_0606_0606L
- * 2. Mask to get the adjustment flags: & 0x1010_1010_1010_1010L
- * 3. Calculate the offset: (m << 1) + (m >> 1) - (m >> 4)
- * - For 0-9: offset = 0
- * - For a-f: offset = 39 (to bridge the gap between '9' and 'a' in ASCII)
- * 4. Add ASCII '0' base (0x30) and the original value
- * 5. Reverse byte order for correct positioning
- */
- long m = (i + 0x0606_0606_0606_0606L) & 0x1010_1010_1010_1010L;
-
- // Calculate final ASCII values and reverse bytes for proper ordering
- return Long.reverseBytes(
- ((m << 1) + (m >> 1) - (m >> 4))
- + 0x3030_3030_3030_3030L // Add ASCII '0' base to all digits
- + i // Add original values
- );
- }
-
/**
* Returns a hash code for this {@code UUID}.
*
diff --git a/src/java.base/share/classes/jdk/internal/util/HexDigits.java b/src/java.base/share/classes/jdk/internal/util/HexDigits.java
index 45b3970405922..4278712df7f64 100644
--- a/src/java.base/share/classes/jdk/internal/util/HexDigits.java
+++ b/src/java.base/share/classes/jdk/internal/util/HexDigits.java
@@ -109,4 +109,78 @@ public static short digitPair(int i, boolean ucase) {
? (short) (v - ((v & 0b0100_0000_0100_0000) >> 1))
: v;
}
+
+ /**
+ * Efficiently converts 8 hexadecimal digits to their ASCII representation using SIMD-style vector operations.
+ * This method processes multiple digits in parallel by treating a long value as eight 8-bit lanes,
+ * achieving significantly better performance compared to traditional loop-based conversion.
+ *
+ * The conversion algorithm works as follows: + *
+ * 1. Input expansion: Each 4-bit hex digit is expanded to 8 bits + * 2. Vector processing: + * - Add 6 to each digit: triggers carry flag for a-f digits + * - Mask with 0x10 pattern to isolate carry flags + * - Calculate ASCII adjustment: (carry << 1) + (carry >> 1) - (carry >> 4) + * - Add ASCII '0' base (0x30) and original value + * 3. Byte order adjustment for final output + *+ * + *
Performance characteristics: + *
ASCII conversion mapping: + *
+ * Input: 0xABCDEF01
+ * Output: 3130666564636261 ('1','0','f','e','d','c','b','a' in ASCII)
+ *
+ *
+ */
+ public static long hex8(long i) {
+ // Expand each 4-bit group into 8 bits, spreading them out in the long value: 0xAABBCCDD -> 0xA0A0B0B0C0C0D0D
+ i = Long.expand(i, 0x0F0F_0F0F_0F0F_0F0FL);
+
+ /*
+ * This method efficiently converts 8 hexadecimal digits simultaneously using vector operations
+ * The algorithm works as follows:
+ *
+ * For input values 0-15:
+ * - For digits 0-9: converts to ASCII '0'-'9' (0x30-0x39)
+ * - For digits 10-15: converts to ASCII 'a'-'f' (0x61-0x66)
+ *
+ * The conversion process:
+ * 1. Add 6 to each 4-bit group: i + 0x0606_0606_0606_0606L
+ * 2. Mask to get the adjustment flags: & 0x1010_1010_1010_1010L
+ * 3. Calculate the offset: (m << 1) + (m >> 1) - (m >> 4)
+ * - For 0-9: offset = 0
+ * - For a-f: offset = 39 (to bridge the gap between '9' and 'a' in ASCII)
+ * 4. Add ASCII '0' base (0x30) and the original value
+ * 5. Reverse byte order for correct positioning
+ */
+ long m = (i + 0x0606_0606_0606_0606L) & 0x1010_1010_1010_1010L;
+
+ // Calculate final ASCII values and reverse bytes for proper ordering
+ return ((m << 1) + (m >> 1) - (m >> 4))
+ + 0x3030_3030_3030_3030L // Add ASCII '0' base to all digits
+ + i; // Add original values
+ }
}
diff --git a/test/micro/org/openjdk/bench/java/lang/Integers.java b/test/micro/org/openjdk/bench/java/lang/Integers.java
index 43ceb5d18d207..3e158a33fabfc 100644
--- a/test/micro/org/openjdk/bench/java/lang/Integers.java
+++ b/test/micro/org/openjdk/bench/java/lang/Integers.java
@@ -44,7 +44,7 @@
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
-@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Fork(3)
public class Integers {
@@ -58,6 +58,9 @@ public class Integers {
private int[] intsSmall;
private int[] intsBig;
private int[] res;
+ private int[] hexsTiny;
+ private int[] hexsSmall;
+ private int[] hexsBig;
@Setup
public void setup() {
@@ -68,11 +71,18 @@ public void setup() {
intsSmall = new int[size];
intsBig = new int[size];
res = new int[size];
+ hexsTiny = new int[size];
+ hexsSmall = new int[size];
+ hexsBig = 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;
+
+ hexsTiny[i] = r.nextInt(0xFF);
+ hexsSmall[i] = 0x100 * i + i + 0x103;
+ hexsBig[i] = ((0x100 * i + i) << 24) + 0x4543 + i * 4;
}
}
@@ -114,6 +124,27 @@ public void toStringBig(Blackhole bh) {
}
}
+ @Benchmark
+ public void toHexStringTiny(Blackhole bh) {
+ for (int i : hexsTiny) {
+ bh.consume(Integer.toHexString(i));
+ }
+ }
+
+ @Benchmark
+ public void toHexStringSmall(Blackhole bh) {
+ for (int i : hexsSmall) {
+ bh.consume(Integer.toHexString(i));
+ }
+ }
+
+ @Benchmark
+ public void toHexStringBig(Blackhole bh) {
+ for (int i : hexsBig) {
+ bh.consume(Integer.toHexString(i));
+ }
+ }
+
/** Performs expand on small values */
@Benchmark
public void expand(Blackhole bh) {
diff --git a/test/micro/org/openjdk/bench/java/lang/Longs.java b/test/micro/org/openjdk/bench/java/lang/Longs.java
index 765d00e9fb90e..77ed5ce681598 100644
--- a/test/micro/org/openjdk/bench/java/lang/Longs.java
+++ b/test/micro/org/openjdk/bench/java/lang/Longs.java
@@ -54,6 +54,8 @@ public class Longs {
private String[] strings;
private long[] longArraySmall;
private long[] longArrayBig;
+ private long[] hexsArraySmall;
+ private long[] hexsArrayBig;
@Setup
public void setup() {
@@ -63,10 +65,14 @@ public void setup() {
res = new long[size];
longArraySmall = new long[size];
longArrayBig = new long[size];
+ hexsArraySmall = new long[size];
+ hexsArrayBig = 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;
+ hexsArraySmall[i] = 0x100L * i + i + 0x103L;
+ hexsArrayBig[i] = ((0x100L * i + i) << 32) + 0x4543 + i * 4L;
}
}
@@ -93,6 +99,20 @@ public void toStringBig(Blackhole bh) {
}
}
+ @Benchmark
+ public void toHexStringSmall(Blackhole bh) {
+ for (long value : hexsArraySmall) {
+ bh.consume(Long.toHexString(value));
+ }
+ }
+
+ @Benchmark
+ public void toHexStringBig(Blackhole bh) {
+ for (long value : hexsArrayBig) {
+ bh.consume(Long.toHexString(value));
+ }
+ }
+
/** Performs expand on small values */
@Benchmark
public void expand(Blackhole bh) {