Skip to content
Closed
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
106 changes: 91 additions & 15 deletions src/java.base/share/classes/java/util/UUID.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.util.HexDigits;
import jdk.internal.util.ByteArrayLittleEndian;

/**
* A class that represents an immutable universally unique identifier (UUID).
Expand Down Expand Up @@ -76,7 +76,6 @@
* @since 1.5
*/
public final class UUID implements java.io.Serializable, Comparable<UUID> {

/**
* Explicit serialVersionUID for interoperability.
*/
Expand Down Expand Up @@ -462,31 +461,108 @@ public long node() {
*/
@Override
public String toString() {
int i0 = (int) (mostSigBits >> 32);
int i1 = (int) mostSigBits;
int i2 = (int) (leastSigBits >> 32);
int i3 = (int) leastSigBits;

byte[] buf = new byte[36];
HexDigits.put4(buf, 0, i0 >> 16);
HexDigits.put4(buf, 4, i0);
buf[8] = '-';
HexDigits.put4(buf, 9, i1 >> 16);
buf[13] = '-';
HexDigits.put4(buf, 14, i1);
buf[18] = '-';
HexDigits.put4(buf, 19, i2 >> 16);
buf[23] = '-';
HexDigits.put4(buf, 24, i2);
HexDigits.put4(buf, 28, i3 >> 16);
HexDigits.put4(buf, 32, i3);

Copy link
Contributor

@j3graham j3graham May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest a comment here along the lines of "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."

// 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);
ByteArrayLittleEndian.setInt(buf, 9, (int) x0);
ByteArrayLittleEndian.setInt(buf, 14, (int) (x0 >>> 32));

long x1 = hex8(leastSigBits >>> 32);
ByteArrayLittleEndian.setInt(buf, 19, (int) (x1));
ByteArrayLittleEndian.setInt(buf, 24, (int) (x1 >>> 32));
ByteArrayLittleEndian.setLong(buf, 28, hex8(leastSigBits));

try {
return jla.uncheckedNewStringNoRepl(buf, StandardCharsets.ISO_8859_1);
} catch (CharacterCodingException cce) {
throw new AssertionError(cce);
}
}

/**
* 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the in-line comment describing the algorithm is enough - doesn't need the details in the javadoc as well.

* <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)
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reference describing this technique? Such as the Hacker's Delight citation used in java.lang.Long?
If not a longer descriptive comment would be good for maintainability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This algorithm was researched and implemented by me, and I have added detailed description and comments

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
16 changes: 0 additions & 16 deletions src/java.base/share/classes/jdk/internal/util/HexDigits.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,6 @@ public static short digitPair(int i, boolean ucase) {
: v;
}

/**
* Insert the unsigned 2-byte integer into the buffer as 4 hexadecimal digit ASCII bytes,
* only least significant 16 bits of {@code value} are used.
* @param buffer byte buffer to copy into
* @param index insert point
* @param value to convert
*/
public static void put4(byte[] buffer, int index, int value) {
// Prepare an int value so C2 generates a 4-byte write instead of two 2-byte writes
int v = (DIGITS[value & 0xff] << 16) | DIGITS[(value >> 8) & 0xff];
buffer[index] = (byte) v;
buffer[index + 1] = (byte) (v >> 8);
buffer[index + 2] = (byte) (v >> 16);
buffer[index + 3] = (byte) (v >> 24);
}

/**
* Insert digits for long value in buffer from high index to low index.
*
Expand Down