Skip to content

Commit

Permalink
8316426: Optimization for HexFormat.formatHex
Browse files Browse the repository at this point in the history
Reviewed-by: liach, rriggs
  • Loading branch information
wenshao authored and cl4es committed Oct 12, 2023
1 parent 32ccf01 commit 9355431
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 27 deletions.
53 changes: 26 additions & 27 deletions src/java.base/share/classes/java/util/HexFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

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

import java.io.IOException;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -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, -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, -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.
Expand Down Expand Up @@ -403,21 +396,20 @@ public <A extends Appendable> A formatHex(A out, byte[] bytes, int fromIndex, in
int length = toIndex - fromIndex;
if (length > 0) {
try {
out.append(prefix);
toHexDigits(out, bytes[fromIndex]);
if (suffix.isEmpty() && delimiter.isEmpty() && prefix.isEmpty()) {
for (int i = 1; i < length; i++) {
toHexDigits(out, bytes[fromIndex + i]);
}
String s = formatOptDelimiter(bytes, fromIndex, toIndex);
if (s != null) {
out.append(s);
} else {
out.append(prefix);
toHexDigits(out, bytes[fromIndex]);
for (int i = 1; i < length; i++) {
out.append(suffix);
out.append(delimiter);
out.append(prefix);
toHexDigits(out, bytes[fromIndex + i]);
}
out.append(suffix);
}
out.append(suffix);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe.getMessage(), ioe);
}
Expand All @@ -438,29 +430,36 @@ public <A extends Appendable> A formatHex(A out, byte[] bytes, int fromIndex, in
* or non-empty prefix or suffix
*/
private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) {
char sep;
byte[] rep;
if (!prefix.isEmpty() || !suffix.isEmpty()) {
return null;
}

boolean ucase = digitCase == Case.UPPERCASE;
int length = toIndex - fromIndex;
if (delimiter.isEmpty()) {
// Allocate the byte array and fill in the hex pairs for each byte
rep = new byte[checkMaxArraySize(length * 2L)];
for (int i = 0; i < length; i++) {
rep[i * 2] = (byte)toHighHexDigit(bytes[fromIndex + i]);
rep[i * 2 + 1] = (byte)toLowHexDigit(bytes[fromIndex + i]);
short pair = HexDigits.digitPair(bytes[fromIndex + i], ucase);
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
// 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[0] = (byte) toHighHexDigit(bytes[fromIndex]);
rep[1] = (byte) toLowHexDigit(bytes[fromIndex]);
short pair = HexDigits.digitPair(bytes[fromIndex], ucase);
rep[0] = (byte)pair;
rep[1] = (byte)(pair >>> 8);
for (int i = 1; i < length; i++) {
rep[i * 3 - 1] = (byte) sep;
rep[i * 3 ] = (byte) toHighHexDigit(bytes[fromIndex + i]);
rep[i * 3 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]);
int pos = i * 3;
pair = HexDigits.digitPair(bytes[fromIndex + i], ucase);
rep[pos - 1] = (byte) sep;
rep[pos] = (byte)pair;
rep[pos + 1] = (byte)(pair >>> 8);
}
} else {
// Delimiter formatting not to a single byte
Expand Down Expand Up @@ -887,7 +886,7 @@ private static int checkDigitCount(int fromIndex, int toIndex, int limit) {
* otherwise {@code false}
*/
public static boolean isHexDigit(int ch) {
return ((ch >>> 8) == 0 && DIGITS[ch] >= 0);
return ((ch >>> 7) == 0 && DIGITS[ch] >= 0);
}

/**
Expand All @@ -905,7 +904,7 @@ public static boolean isHexDigit(int ch) {
*/
public static int fromHexDigit(int ch) {
int value;
if ((ch >>> 8) == 0 && (value = DIGITS[ch]) >= 0) {
if ((ch >>> 7) == 0 && (value = DIGITS[ch]) >= 0) {
return value;
}
throw new NumberFormatException("not a hexadecimal digit: \"" + (char) ch + "\" = " + ch);
Expand Down
24 changes: 24 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 @@ -91,6 +91,30 @@ public final class HexDigits implements Digits {
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.
* {@code b0} is the most significant byte and {@code b1} is the least significant byte.
Expand Down
19 changes: 19 additions & 0 deletions test/micro/org/openjdk/bench/java/util/HexFormatBench.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ public StringBuilder appenderUpperCached() {
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
public void toHexLower(Blackhole bh) {
Expand Down

1 comment on commit 9355431

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.