8316426: Optimization for HexFormat.formatHex

Reviewed-by: liach, rriggs
This commit is contained in:
wenshao 2023-10-12 13:54:09 +00:00 committed by Claes Redestad
parent 32ccf018eb
commit 935543146b
3 changed files with 69 additions and 27 deletions

View file

@ -27,6 +27,7 @@ package java.util;
import jdk.internal.access.JavaLangAccess; import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets; import jdk.internal.access.SharedSecrets;
import jdk.internal.util.HexDigits;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
@ -150,15 +151,7 @@ public final class HexFormat {
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
}; };
/** /**
* Format each byte of an array as a pair of hexadecimal digits. * Format each byte of an array as a pair of hexadecimal digits.
@ -403,21 +396,20 @@ public final class HexFormat {
int length = toIndex - fromIndex; int length = toIndex - fromIndex;
if (length > 0) { if (length > 0) {
try { try {
out.append(prefix); String s = formatOptDelimiter(bytes, fromIndex, toIndex);
toHexDigits(out, bytes[fromIndex]); if (s != null) {
if (suffix.isEmpty() && delimiter.isEmpty() && prefix.isEmpty()) { out.append(s);
for (int i = 1; i < length; i++) {
toHexDigits(out, bytes[fromIndex + i]);
}
} else { } else {
out.append(prefix);
toHexDigits(out, bytes[fromIndex]);
for (int i = 1; i < length; i++) { for (int i = 1; i < length; i++) {
out.append(suffix); out.append(suffix);
out.append(delimiter); out.append(delimiter);
out.append(prefix); out.append(prefix);
toHexDigits(out, bytes[fromIndex + i]); toHexDigits(out, bytes[fromIndex + i]);
} }
out.append(suffix);
} }
out.append(suffix);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new UncheckedIOException(ioe.getMessage(), ioe); throw new UncheckedIOException(ioe.getMessage(), ioe);
} }
@ -438,29 +430,36 @@ public final class HexFormat {
* or non-empty prefix or suffix * or non-empty prefix or suffix
*/ */
private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) { private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) {
char sep;
byte[] rep; byte[] rep;
if (!prefix.isEmpty() || !suffix.isEmpty()) { if (!prefix.isEmpty() || !suffix.isEmpty()) {
return null; return null;
} }
boolean ucase = digitCase == Case.UPPERCASE;
int length = toIndex - fromIndex; int length = toIndex - fromIndex;
if (delimiter.isEmpty()) { if (delimiter.isEmpty()) {
// Allocate the byte array and fill in the hex pairs for each byte // Allocate the byte array and fill in the hex pairs for each byte
rep = new byte[checkMaxArraySize(length * 2L)]; rep = new byte[checkMaxArraySize(length * 2L)];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
rep[i * 2] = (byte)toHighHexDigit(bytes[fromIndex + i]); short pair = HexDigits.digitPair(bytes[fromIndex + i], ucase);
rep[i * 2 + 1] = (byte)toLowHexDigit(bytes[fromIndex + i]); int pos = i * 2;
rep[pos] = (byte)pair;
rep[pos + 1] = (byte)(pair >>> 8);
} }
} else if (delimiter.length() == 1 && delimiter.charAt(0) < 256) { } else if (delimiter.length() == 1 && (sep = delimiter.charAt(0)) < 256) {
// Allocate the byte array and fill in the characters for the first byte // Allocate the byte array and fill in the characters for the first byte
// Then insert the delimiter and hexadecimal characters for each of the remaining bytes // Then insert the delimiter and hexadecimal characters for each of the remaining bytes
char sep = delimiter.charAt(0);
rep = new byte[checkMaxArraySize(length * 3L - 1L)]; rep = new byte[checkMaxArraySize(length * 3L - 1L)];
rep[0] = (byte) toHighHexDigit(bytes[fromIndex]); short pair = HexDigits.digitPair(bytes[fromIndex], ucase);
rep[1] = (byte) toLowHexDigit(bytes[fromIndex]); rep[0] = (byte)pair;
rep[1] = (byte)(pair >>> 8);
for (int i = 1; i < length; i++) { for (int i = 1; i < length; i++) {
rep[i * 3 - 1] = (byte) sep; int pos = i * 3;
rep[i * 3 ] = (byte) toHighHexDigit(bytes[fromIndex + i]); pair = HexDigits.digitPair(bytes[fromIndex + i], ucase);
rep[i * 3 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]); rep[pos - 1] = (byte) sep;
rep[pos] = (byte)pair;
rep[pos + 1] = (byte)(pair >>> 8);
} }
} else { } else {
// Delimiter formatting not to a single byte // Delimiter formatting not to a single byte
@ -887,7 +886,7 @@ public final class HexFormat {
* otherwise {@code false} * otherwise {@code false}
*/ */
public static boolean isHexDigit(int ch) { public static boolean isHexDigit(int ch) {
return ((ch >>> 8) == 0 && DIGITS[ch] >= 0); return ((ch >>> 7) == 0 && DIGITS[ch] >= 0);
} }
/** /**
@ -905,7 +904,7 @@ public final class HexFormat {
*/ */
public static int fromHexDigit(int ch) { public static int fromHexDigit(int ch) {
int value; int value;
if ((ch >>> 8) == 0 && (value = DIGITS[ch]) >= 0) { if ((ch >>> 7) == 0 && (value = DIGITS[ch]) >= 0) {
return value; return value;
} }
throw new NumberFormatException("not a hexadecimal digit: \"" + (char) ch + "\" = " + ch); throw new NumberFormatException("not a hexadecimal digit: \"" + (char) ch + "\" = " + ch);

View file

@ -91,6 +91,30 @@ public final class HexDigits implements Digits {
private HexDigits() { private HexDigits() {
} }
/**
* For values from 0 to 255 return a short encoding a pair of hex ASCII-encoded digit characters in little-endian
* @param i value to convert
* @param ucase true uppper case, false lower case
* @return a short encoding a pair of hex ASCII-encoded digit characters
*/
public static short digitPair(int i, boolean ucase) {
/*
* 0b0100_0000_0100_0000 is a selector that selects letters (1 << 6),
* uppercase or not, and shifting it right by 1 bit incidentally
* becomes a bit offset between cases (1 << 5).
*
* ([0-9] & 0b100_0000) >> 1 => 0
* ([a-f] & 0b100_0000) >> 1 => 32
*
* [0-9] - 0 => [0-9]
* [a-f] - 32 => [A-F]
*/
short v = DIGITS[i & 0xff];
return ucase
? (short) (v - ((v & 0b0100_0000_0100_0000) >> 1))
: v;
}
/** /**
* Return a little-endian packed integer for the 4 ASCII bytes for an input unsigned 2-byte integer. * Return a little-endian packed integer for the 4 ASCII bytes for an input unsigned 2-byte integer.
* {@code b0} is the most significant byte and {@code b1} is the least significant byte. * {@code b0} is the most significant byte and {@code b1} is the least significant byte.

View file

@ -93,6 +93,25 @@ public class HexFormatBench {
return UPPER_FORMATTER.formatHex(builder, bytes); return UPPER_FORMATTER.formatHex(builder, bytes);
} }
@Benchmark
public String formatLower() {
return HexFormat.of().formatHex(bytes);
}
@Benchmark
public String formatUpper() {
return HexFormat.of().withUpperCase().formatHex(bytes);
}
@Benchmark
public String formatLowerCached() {
return LOWER_FORMATTER.formatHex(bytes);
}
@Benchmark
public String formatUpperCached() {
return UPPER_FORMATTER.formatHex(bytes);
}
@Benchmark @Benchmark
public void toHexLower(Blackhole bh) { public void toHexLower(Blackhole bh) {