From ca0433d7f421fee1bd5ba1b22fe718da048a0dc8 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Fri, 23 May 2025 08:38:49 +0800 Subject: [PATCH] use HexDigits hex8 --- .../share/classes/java/util/UUID.java | 86 +-------------- .../classes/jdk/internal/util/HexDigits.java | 100 +++++++++++++----- 2 files changed, 78 insertions(+), 108 deletions(-) diff --git a/src/java.base/share/classes/java/util/UUID.java b/src/java.base/share/classes/java/util/UUID.java index 5961fce9cb2..bcb13374478 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 java.security.*; 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). @@ -469,15 +470,15 @@ public final class UUID implements java.io.Serializable, Comparable { // 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.setLong(buf, 0, HexDigits.hex8(mostSigBits >>> 32)); + long x0 = HexDigits.hex8(mostSigBits); ByteArrayLittleEndian.setInt(buf, 9, (int) x0); ByteArrayLittleEndian.setInt(buf, 14, (int) (x0 >>> 32)); - long x1 = hex8(leastSigBits >>> 32); + long x1 = 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, HexDigits.hex8(leastSigBits)); try { return jla.uncheckedNewStringNoRepl(buf, StandardCharsets.ISO_8859_1); @@ -486,83 +487,6 @@ public final class UUID implements java.io.Serializable, Comparable { } } - /** - * 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: - *

    - *
  • Processes 8 digits in parallel using vector operations - *
  • Avoids branching and loops completely - *
  • Uses only integer arithmetic and bit operations - *
  • Constant time execution regardless of input values - *
- * - *

ASCII conversion mapping: - *

    - *
  • Digits 0-9 → ASCII '0'-'9' (0x30-0x39) - *
  • Digits a-f → ASCII 'a'-'f' (0x61-0x66) - *
- * - * @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 - *
-     * 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 1f73b42811f..61d9ca09867 100644 --- a/src/java.base/share/classes/jdk/internal/util/HexDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/HexDigits.java @@ -179,36 +179,82 @@ public final class HexDigits { 67 - Long.numberOfLeadingZeros(value) >> 2; } + + /** - * Extract the least significant 4 bytes from the input integer i, convert each byte into its corresponding 2-digit - * hexadecimal representation, concatenate these hexadecimal strings into one continuous string, and then interpret - * this string as a hexadecimal number to form and return a long value. + * 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: + *

    + *
  • Processes 8 digits in parallel using vector operations + *
  • Avoids branching and loops completely + *
  • Uses only integer arithmetic and bit operations + *
  • Constant time execution regardless of input values + *
+ * + *

ASCII conversion mapping: + *

    + *
  • Digits 0-9 → ASCII '0'-'9' (0x30-0x39) + *
  • Digits a-f → ASCII 'a'-'f' (0x61-0x66) + *
+ * + * @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 + *
+     * Input:  0xABCDEF01
+     * Output: 3130666564636261 ('1','0','f','e','d','c','b','a' in ASCII)
+     * 
+ * + * @see Long#reverseBytes(long) */ - public static long hex8(long i) { - long x = Long.expand(i, 0x0F0F_0F0F_0F0F_0F0FL); + 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); + /* - Use long to simulate vector operations and generate 8 hexadecimal characters at a time. - ------------ - 0 = 0b0000_0000 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '0' - 1 = 0b0000_0001 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '1' - 2 = 0b0000_0010 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '2' - 3 = 0b0000_0011 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '3' - 4 = 0b0000_0100 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '4' - 5 = 0b0000_0101 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '5' - 6 = 0b0000_0110 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '6' - 7 = 0b0000_0111 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '7' - 8 = 0b0000_1000 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '8' - 9 = 0b0000_1001 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 0 + 0x30 + (i & 0xF) => '9' - 10 = 0b0000_1010 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 39 + 0x30 + (i & 0xF) => 'a' - 11 = 0b0000_1011 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 39 + 0x30 + (i & 0xF) => 'b' - 12 = 0b0000_1100 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 39 + 0x30 + (i & 0xF) => 'c' - 13 = 0b0000_1101 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 39 + 0x30 + (i & 0xF) => 'd' - 14 = 0b0000_1110 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 39 + 0x30 + (i & 0xF) => 'e' - 15 = 0b0000_1111 => m = ((i + 6) & 0x10); (m << 1) + (m >> 1) - (m >> 4) => 39 + 0x30 + (i & 0xF) => 'f' + * 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 = (x + 0x0606_0606_0606_0606L) & 0x1010_1010_1010_1010L; - return ((m << 1) + (m >> 1) - (m >> 4)) - + 0x3030_3030_3030_3030L - + x; + 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 + ); } }