Skip to content

Commit 796ec5e

Browse files
committed
8353741: Eliminate table lookup in UUID.toString
Reviewed-by: rriggs
1 parent fdda766 commit 796ec5e

File tree

2 files changed

+91
-31
lines changed

2 files changed

+91
-31
lines changed

src/java.base/share/classes/java/util/UUID.java

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
import jdk.internal.access.JavaLangAccess;
3333
import jdk.internal.access.SharedSecrets;
34-
import jdk.internal.util.HexDigits;
34+
import jdk.internal.util.ByteArrayLittleEndian;
3535

3636
/**
3737
* A class that represents an immutable universally unique identifier (UUID).
@@ -76,7 +76,6 @@
7676
* @since 1.5
7777
*/
7878
public final class UUID implements java.io.Serializable, Comparable<UUID> {
79-
8079
/**
8180
* Explicit serialVersionUID for interoperability.
8281
*/
@@ -462,31 +461,108 @@ public long node() {
462461
*/
463462
@Override
464463
public String toString() {
465-
int i0 = (int) (mostSigBits >> 32);
466-
int i1 = (int) mostSigBits;
467-
int i2 = (int) (leastSigBits >> 32);
468-
int i3 = (int) leastSigBits;
469-
470464
byte[] buf = new byte[36];
471-
HexDigits.put4(buf, 0, i0 >> 16);
472-
HexDigits.put4(buf, 4, i0);
473465
buf[8] = '-';
474-
HexDigits.put4(buf, 9, i1 >> 16);
475466
buf[13] = '-';
476-
HexDigits.put4(buf, 14, i1);
477467
buf[18] = '-';
478-
HexDigits.put4(buf, 19, i2 >> 16);
479468
buf[23] = '-';
480-
HexDigits.put4(buf, 24, i2);
481-
HexDigits.put4(buf, 28, i3 >> 16);
482-
HexDigits.put4(buf, 32, i3);
469+
470+
// Although the UUID byte ordering is defined to be big-endian, ByteArrayLittleEndian is used here to optimize
471+
// for the most common architectures. hex8 reverses the order internally.
472+
ByteArrayLittleEndian.setLong(buf, 0, hex8(mostSigBits >>> 32));
473+
long x0 = hex8(mostSigBits);
474+
ByteArrayLittleEndian.setInt(buf, 9, (int) x0);
475+
ByteArrayLittleEndian.setInt(buf, 14, (int) (x0 >>> 32));
476+
477+
long x1 = hex8(leastSigBits >>> 32);
478+
ByteArrayLittleEndian.setInt(buf, 19, (int) (x1));
479+
ByteArrayLittleEndian.setInt(buf, 24, (int) (x1 >>> 32));
480+
ByteArrayLittleEndian.setLong(buf, 28, hex8(leastSigBits));
481+
483482
try {
484483
return jla.uncheckedNewStringNoRepl(buf, StandardCharsets.ISO_8859_1);
485484
} catch (CharacterCodingException cce) {
486485
throw new AssertionError(cce);
487486
}
488487
}
489488

489+
/**
490+
* Efficiently converts 8 hexadecimal digits to their ASCII representation using SIMD-style vector operations.
491+
* This method processes multiple digits in parallel by treating a long value as eight 8-bit lanes,
492+
* achieving significantly better performance compared to traditional loop-based conversion.
493+
*
494+
* <p>The conversion algorithm works as follows:
495+
* <pre>
496+
* 1. Input expansion: Each 4-bit hex digit is expanded to 8 bits
497+
* 2. Vector processing:
498+
* - Add 6 to each digit: triggers carry flag for a-f digits
499+
* - Mask with 0x10 pattern to isolate carry flags
500+
* - Calculate ASCII adjustment: (carry << 1) + (carry >> 1) - (carry >> 4)
501+
* - Add ASCII '0' base (0x30) and original value
502+
* 3. Byte order adjustment for final output
503+
* </pre>
504+
*
505+
* <p>Performance characteristics:
506+
* <ul>
507+
* <li>Processes 8 digits in parallel using vector operations
508+
* <li>Avoids branching and loops completely
509+
* <li>Uses only integer arithmetic and bit operations
510+
* <li>Constant time execution regardless of input values
511+
* </ul>
512+
*
513+
* <p>ASCII conversion mapping:
514+
* <ul>
515+
* <li>Digits 0-9 → ASCII '0'-'9' (0x30-0x39)
516+
* <li>Digits a-f → ASCII 'a'-'f' (0x61-0x66)
517+
* </ul>
518+
*
519+
* @param input A long containing 8 hex digits (each digit must be 0-15)
520+
* @return A long containing 8 ASCII bytes representing the hex digits
521+
*
522+
* @implNote The implementation leverages CPU vector processing capabilities through
523+
* long integer operations. The algorithm is based on the observation that
524+
* ASCII hex digits have a specific pattern that can be computed efficiently
525+
* using carry flag manipulation.
526+
*
527+
* @example
528+
* <pre>
529+
* Input: 0xABCDEF01
530+
* Output: 3130666564636261 ('1','0','f','e','d','c','b','a' in ASCII)
531+
* </pre>
532+
*
533+
* @see Long#reverseBytes(long)
534+
*/
535+
private static long hex8(long i) {
536+
// Expand each 4-bit group into 8 bits, spreading them out in the long value: 0xAABBCCDD -> 0xA0A0B0B0C0C0D0D
537+
i = Long.expand(i, 0x0F0F_0F0F_0F0F_0F0FL);
538+
539+
/*
540+
* This method efficiently converts 8 hexadecimal digits simultaneously using vector operations
541+
* The algorithm works as follows:
542+
*
543+
* For input values 0-15:
544+
* - For digits 0-9: converts to ASCII '0'-'9' (0x30-0x39)
545+
* - For digits 10-15: converts to ASCII 'a'-'f' (0x61-0x66)
546+
*
547+
* The conversion process:
548+
* 1. Add 6 to each 4-bit group: i + 0x0606_0606_0606_0606L
549+
* 2. Mask to get the adjustment flags: & 0x1010_1010_1010_1010L
550+
* 3. Calculate the offset: (m << 1) + (m >> 1) - (m >> 4)
551+
* - For 0-9: offset = 0
552+
* - For a-f: offset = 39 (to bridge the gap between '9' and 'a' in ASCII)
553+
* 4. Add ASCII '0' base (0x30) and the original value
554+
* 5. Reverse byte order for correct positioning
555+
*/
556+
long m = (i + 0x0606_0606_0606_0606L) & 0x1010_1010_1010_1010L;
557+
558+
// Calculate final ASCII values and reverse bytes for proper ordering
559+
return Long.reverseBytes(
560+
((m << 1) + (m >> 1) - (m >> 4))
561+
+ 0x3030_3030_3030_3030L // Add ASCII '0' base to all digits
562+
+ i // Add original values
563+
);
564+
}
565+
490566
/**
491567
* Returns a hash code for this {@code UUID}.
492568
*

src/java.base/share/classes/jdk/internal/util/HexDigits.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,22 +113,6 @@ public static short digitPair(int i, boolean ucase) {
113113
: v;
114114
}
115115

116-
/**
117-
* Insert the unsigned 2-byte integer into the buffer as 4 hexadecimal digit ASCII bytes,
118-
* only least significant 16 bits of {@code value} are used.
119-
* @param buffer byte buffer to copy into
120-
* @param index insert point
121-
* @param value to convert
122-
*/
123-
public static void put4(byte[] buffer, int index, int value) {
124-
// Prepare an int value so C2 generates a 4-byte write instead of two 2-byte writes
125-
int v = (DIGITS[value & 0xff] << 16) | DIGITS[(value >> 8) & 0xff];
126-
buffer[index] = (byte) v;
127-
buffer[index + 1] = (byte) (v >> 8);
128-
buffer[index + 2] = (byte) (v >> 16);
129-
buffer[index + 3] = (byte) (v >> 24);
130-
}
131-
132116
/**
133117
* Insert digits for long value in buffer from high index to low index.
134118
*

0 commit comments

Comments
 (0)