Skip to content
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
20 changes: 19 additions & 1 deletion src/java.base/share/classes/java/lang/Integer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down
36 changes: 35 additions & 1 deletion src/java.base/share/classes/java/lang/Long.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down
88 changes: 6 additions & 82 deletions src/java.base/share/classes/java/util/UUID.java
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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);
Expand All @@ -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.
*
* <p>The conversion algorithm works as follows:
* <pre>
* 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
* </pre>
*
* <p>Performance characteristics:
* <ul>
* <li>Processes 8 digits in parallel using vector operations
* <li>Avoids branching and loops completely
* <li>Uses only integer arithmetic and bit operations
* <li>Constant time execution regardless of input values
* </ul>
*
* <p>ASCII conversion mapping:
* <ul>
* <li>Digits 0-9 → ASCII '0'-'9' (0x30-0x39)
* <li>Digits a-f → ASCII 'a'-'f' (0x61-0x66)
* </ul>
*
* @param input A long containing 8 hex digits (each digit must be 0-15)
* @return A long containing 8 ASCII bytes representing the hex digits
*
* @implNote The implementation leverages CPU vector processing capabilities through
* long integer operations. The algorithm is based on the observation that
* ASCII hex digits have a specific pattern that can be computed efficiently
* using carry flag manipulation.
*
* @example
* <pre>
* Input: 0xABCDEF01
* Output: 3130666564636261 ('1','0','f','e','d','c','b','a' in ASCII)
* </pre>
*
* @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}.
*
Expand Down
74 changes: 74 additions & 0 deletions src/java.base/share/classes/jdk/internal/util/HexDigits.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>The conversion algorithm works as follows:
* <pre>
* 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
* </pre>
*
* <p>Performance characteristics:
* <ul>
* <li>Processes 8 digits in parallel using vector operations
* <li>Avoids branching and loops completely
* <li>Uses only integer arithmetic and bit operations
* <li>Constant time execution regardless of input values
* </ul>
*
* <p>ASCII conversion mapping:
* <ul>
* <li>Digits 0-9 → ASCII '0'-'9' (0x30-0x39)
* <li>Digits a-f → ASCII 'a'-'f' (0x61-0x66)
* </ul>
*
* @param input A long containing 8 hex digits (each digit must be 0-15)
* @return A long containing 8 ASCII bytes representing the hex digits
*
* @implNote The implementation leverages CPU vector processing capabilities through
* long integer operations. The algorithm is based on the observation that
* ASCII hex digits have a specific pattern that can be computed efficiently
* using carry flag manipulation.
*
* @example
* <pre>
* Input: 0xABCDEF01
* Output: 3130666564636261 ('1','0','f','e','d','c','b','a' in ASCII)
* </pre>
*
*/
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
}
}
33 changes: 32 additions & 1 deletion test/micro/org/openjdk/bench/java/lang/Integers.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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() {
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions test/micro/org/openjdk/bench/java/lang/Longs.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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;
}
}

Expand All @@ -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) {
Expand Down