8315789: Minor HexFormat performance improvements
Reviewed-by: rriggs
This commit is contained in:
parent
ce93d27fe5
commit
92ad4a2399
@ -140,14 +140,6 @@ public final class HexFormat {
|
|||||||
// Access to create strings from a byte array.
|
// Access to create strings from a byte array.
|
||||||
private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
|
private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
|
||||||
|
|
||||||
private static final byte[] UPPERCASE_DIGITS = {
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
|
||||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
|
||||||
};
|
|
||||||
private static final byte[] LOWERCASE_DIGITS = {
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
|
||||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
|
|
||||||
};
|
|
||||||
// Analysis has shown that generating the whole array allows the JIT to generate
|
// Analysis has shown that generating the whole array allows the JIT to generate
|
||||||
// better code compared to a slimmed down array, such as one cutting off after 'f'
|
// better code compared to a slimmed down array, such as one cutting off after 'f'
|
||||||
private static final byte[] DIGITS = {
|
private static final byte[] DIGITS = {
|
||||||
@ -173,14 +165,22 @@ public final class HexFormat {
|
|||||||
* The hexadecimal characters are from lowercase alpha digits.
|
* The hexadecimal characters are from lowercase alpha digits.
|
||||||
*/
|
*/
|
||||||
private static final HexFormat HEX_FORMAT =
|
private static final HexFormat HEX_FORMAT =
|
||||||
new HexFormat("", "", "", LOWERCASE_DIGITS);
|
new HexFormat("", "", "", Case.LOWERCASE);
|
||||||
|
|
||||||
|
private static final HexFormat HEX_UPPER_FORMAT =
|
||||||
|
new HexFormat("", "", "", Case.UPPERCASE);
|
||||||
|
|
||||||
private static final byte[] EMPTY_BYTES = {};
|
private static final byte[] EMPTY_BYTES = {};
|
||||||
|
|
||||||
private final String delimiter;
|
private final String delimiter;
|
||||||
private final String prefix;
|
private final String prefix;
|
||||||
private final String suffix;
|
private final String suffix;
|
||||||
private final byte[] digits;
|
private final Case digitCase;
|
||||||
|
|
||||||
|
private enum Case {
|
||||||
|
LOWERCASE,
|
||||||
|
UPPERCASE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a HexFormat with a delimiter, prefix, suffix, and array of digits.
|
* Returns a HexFormat with a delimiter, prefix, suffix, and array of digits.
|
||||||
@ -188,14 +188,14 @@ public final class HexFormat {
|
|||||||
* @param delimiter a delimiter, non-null
|
* @param delimiter a delimiter, non-null
|
||||||
* @param prefix a prefix, non-null
|
* @param prefix a prefix, non-null
|
||||||
* @param suffix a suffix, non-null
|
* @param suffix a suffix, non-null
|
||||||
* @param digits byte array of digits indexed by low nibble, non-null
|
* @param digitCase enum indicating how to case digits
|
||||||
* @throws NullPointerException if any argument is null
|
* @throws NullPointerException if any argument is null
|
||||||
*/
|
*/
|
||||||
private HexFormat(String delimiter, String prefix, String suffix, byte[] digits) {
|
private HexFormat(String delimiter, String prefix, String suffix, Case digitCase) {
|
||||||
this.delimiter = Objects.requireNonNull(delimiter, "delimiter");
|
this.delimiter = Objects.requireNonNull(delimiter, "delimiter");
|
||||||
this.prefix = Objects.requireNonNull(prefix, "prefix");
|
this.prefix = Objects.requireNonNull(prefix, "prefix");
|
||||||
this.suffix = Objects.requireNonNull(suffix, "suffix");
|
this.suffix = Objects.requireNonNull(suffix, "suffix");
|
||||||
this.digits = digits;
|
this.digitCase = digitCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,7 +224,7 @@ public final class HexFormat {
|
|||||||
* @return a {@link HexFormat} with the delimiter and lowercase characters
|
* @return a {@link HexFormat} with the delimiter and lowercase characters
|
||||||
*/
|
*/
|
||||||
public static HexFormat ofDelimiter(String delimiter) {
|
public static HexFormat ofDelimiter(String delimiter) {
|
||||||
return new HexFormat(delimiter, "", "", LOWERCASE_DIGITS);
|
return new HexFormat(delimiter, "", "", Case.LOWERCASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,7 +233,7 @@ public final class HexFormat {
|
|||||||
* @return a copy of this {@code HexFormat} with the delimiter
|
* @return a copy of this {@code HexFormat} with the delimiter
|
||||||
*/
|
*/
|
||||||
public HexFormat withDelimiter(String delimiter) {
|
public HexFormat withDelimiter(String delimiter) {
|
||||||
return new HexFormat(delimiter, this.prefix, this.suffix, this.digits);
|
return new HexFormat(delimiter, this.prefix, this.suffix, this.digitCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -243,7 +243,7 @@ public final class HexFormat {
|
|||||||
* @return a copy of this {@code HexFormat} with the prefix
|
* @return a copy of this {@code HexFormat} with the prefix
|
||||||
*/
|
*/
|
||||||
public HexFormat withPrefix(String prefix) {
|
public HexFormat withPrefix(String prefix) {
|
||||||
return new HexFormat(this.delimiter, prefix, this.suffix, this.digits);
|
return new HexFormat(this.delimiter, prefix, this.suffix, this.digitCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -253,7 +253,7 @@ public final class HexFormat {
|
|||||||
* @return a copy of this {@code HexFormat} with the suffix
|
* @return a copy of this {@code HexFormat} with the suffix
|
||||||
*/
|
*/
|
||||||
public HexFormat withSuffix(String suffix) {
|
public HexFormat withSuffix(String suffix) {
|
||||||
return new HexFormat(this.delimiter, this.prefix, suffix, this.digits);
|
return new HexFormat(this.delimiter, this.prefix, suffix, this.digitCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -263,7 +263,9 @@ public final class HexFormat {
|
|||||||
* @return a copy of this {@code HexFormat} with uppercase hexadecimal characters
|
* @return a copy of this {@code HexFormat} with uppercase hexadecimal characters
|
||||||
*/
|
*/
|
||||||
public HexFormat withUpperCase() {
|
public HexFormat withUpperCase() {
|
||||||
return new HexFormat(this.delimiter, this.prefix, this.suffix, UPPERCASE_DIGITS);
|
if (this == HEX_FORMAT)
|
||||||
|
return HEX_UPPER_FORMAT;
|
||||||
|
return new HexFormat(this.delimiter, this.prefix, this.suffix, Case.UPPERCASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -273,7 +275,7 @@ public final class HexFormat {
|
|||||||
* @return a copy of this {@code HexFormat} with lowercase hexadecimal characters
|
* @return a copy of this {@code HexFormat} with lowercase hexadecimal characters
|
||||||
*/
|
*/
|
||||||
public HexFormat withLowerCase() {
|
public HexFormat withLowerCase() {
|
||||||
return new HexFormat(this.delimiter, this.prefix, this.suffix, LOWERCASE_DIGITS);
|
return new HexFormat(this.delimiter, this.prefix, this.suffix, Case.LOWERCASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -311,7 +313,7 @@ public final class HexFormat {
|
|||||||
* otherwise {@code false}
|
* otherwise {@code false}
|
||||||
*/
|
*/
|
||||||
public boolean isUpperCase() {
|
public boolean isUpperCase() {
|
||||||
return Arrays.equals(digits, UPPERCASE_DIGITS);
|
return digitCase == Case.UPPERCASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -401,16 +403,17 @@ public final class HexFormat {
|
|||||||
int length = toIndex - fromIndex;
|
int length = toIndex - fromIndex;
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
try {
|
try {
|
||||||
String between = suffix + delimiter + prefix;
|
|
||||||
out.append(prefix);
|
out.append(prefix);
|
||||||
toHexDigits(out, bytes[fromIndex]);
|
toHexDigits(out, bytes[fromIndex]);
|
||||||
if (between.isEmpty()) {
|
if (suffix.isEmpty() && delimiter.isEmpty() && prefix.isEmpty()) {
|
||||||
for (int i = 1; i < length; i++) {
|
for (int i = 1; i < length; i++) {
|
||||||
toHexDigits(out, bytes[fromIndex + i]);
|
toHexDigits(out, bytes[fromIndex + i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 1; i < length; i++) {
|
for (int i = 1; i < length; i++) {
|
||||||
out.append(between);
|
out.append(suffix);
|
||||||
|
out.append(delimiter);
|
||||||
|
out.append(prefix);
|
||||||
toHexDigits(out, bytes[fromIndex + i]);
|
toHexDigits(out, bytes[fromIndex + i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -631,7 +634,14 @@ public final class HexFormat {
|
|||||||
* @return the hexadecimal character for the low 4 bits {@code 0-3} of the value
|
* @return the hexadecimal character for the low 4 bits {@code 0-3} of the value
|
||||||
*/
|
*/
|
||||||
public char toLowHexDigit(int value) {
|
public char toLowHexDigit(int value) {
|
||||||
return (char)digits[value & 0xf];
|
value = value & 0xf;
|
||||||
|
if (value < 10) {
|
||||||
|
return (char)('0' + value);
|
||||||
|
}
|
||||||
|
if (digitCase == Case.LOWERCASE) {
|
||||||
|
return (char)('a' - 10 + value);
|
||||||
|
}
|
||||||
|
return (char)('A' - 10 + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -645,7 +655,14 @@ public final class HexFormat {
|
|||||||
* @return the hexadecimal character for the bits {@code 4-7} of the value
|
* @return the hexadecimal character for the bits {@code 4-7} of the value
|
||||||
*/
|
*/
|
||||||
public char toHighHexDigit(int value) {
|
public char toHighHexDigit(int value) {
|
||||||
return (char)digits[(value >> 4) & 0xf];
|
value = (value >> 4) & 0xf;
|
||||||
|
if (value < 10) {
|
||||||
|
return (char)('0' + value);
|
||||||
|
}
|
||||||
|
if (digitCase == Case.LOWERCASE) {
|
||||||
|
return (char)('a' - 10 + value);
|
||||||
|
}
|
||||||
|
return (char)('A' - 10 + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1052,7 +1069,7 @@ public final class HexFormat {
|
|||||||
if (o == null || getClass() != o.getClass())
|
if (o == null || getClass() != o.getClass())
|
||||||
return false;
|
return false;
|
||||||
HexFormat otherHex = (HexFormat) o;
|
HexFormat otherHex = (HexFormat) o;
|
||||||
return Arrays.equals(digits, otherHex.digits) &&
|
return digitCase == otherHex.digitCase &&
|
||||||
delimiter.equals(otherHex.delimiter) &&
|
delimiter.equals(otherHex.delimiter) &&
|
||||||
prefix.equals(otherHex.prefix) &&
|
prefix.equals(otherHex.prefix) &&
|
||||||
suffix.equals(otherHex.suffix);
|
suffix.equals(otherHex.suffix);
|
||||||
@ -1066,7 +1083,7 @@ public final class HexFormat {
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = Objects.hash(delimiter, prefix, suffix);
|
int result = Objects.hash(delimiter, prefix, suffix);
|
||||||
result = 31 * result + Boolean.hashCode(Arrays.equals(digits, UPPERCASE_DIGITS));
|
result = 31 * result + Boolean.hashCode(digitCase == Case.UPPERCASE);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1078,7 +1095,7 @@ public final class HexFormat {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return escapeNL("uppercase: " + Arrays.equals(digits, UPPERCASE_DIGITS) +
|
return escapeNL("uppercase: " + (digitCase == Case.UPPERCASE) +
|
||||||
", delimiter: \"" + delimiter +
|
", delimiter: \"" + delimiter +
|
||||||
"\", prefix: \"" + prefix +
|
"\", prefix: \"" + prefix +
|
||||||
"\", suffix: \"" + suffix + "\"");
|
"\", suffix: \"" + suffix + "\"");
|
||||||
|
156
test/micro/org/openjdk/bench/java/util/HexFormatBench.java
Normal file
156
test/micro/org/openjdk/bench/java/util/HexFormatBench.java
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
package org.openjdk.bench.java.util;
|
||||||
|
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
import org.openjdk.jmh.infra.Blackhole;
|
||||||
|
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests java.net.URLEncoder.encode and Decoder.decode.
|
||||||
|
*/
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
@State(Scope.Thread)
|
||||||
|
@Warmup(iterations = 5, time = 1)
|
||||||
|
@Measurement(iterations = 5, time = 1)
|
||||||
|
@Fork(value = 3)
|
||||||
|
public class HexFormatBench {
|
||||||
|
|
||||||
|
@Param({"512"})
|
||||||
|
public int size;
|
||||||
|
|
||||||
|
public byte[] bytes;
|
||||||
|
|
||||||
|
public StringBuilder builder = new StringBuilder(size * 2);
|
||||||
|
|
||||||
|
HexFormat LOWER_FORMATTER = HexFormat.of();
|
||||||
|
HexFormat UPPER_FORMATTER = HexFormat.of().withUpperCase();
|
||||||
|
|
||||||
|
@Setup
|
||||||
|
public void setupStrings() {
|
||||||
|
Random random = new Random(3);
|
||||||
|
bytes = new byte[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
bytes[i] = (byte)random.nextInt(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public StringBuilder appenderLower() {
|
||||||
|
builder.setLength(0);
|
||||||
|
return HexFormat.of().formatHex(builder, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public StringBuilder appenderUpper() {
|
||||||
|
builder.setLength(0);
|
||||||
|
return HexFormat.of().withUpperCase().formatHex(builder, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public StringBuilder appenderLowerCached() {
|
||||||
|
builder.setLength(0);
|
||||||
|
return LOWER_FORMATTER.formatHex(builder, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public StringBuilder appenderUpperCached() {
|
||||||
|
builder.setLength(0);
|
||||||
|
return UPPER_FORMATTER.formatHex(builder, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexLower(Blackhole bh) {
|
||||||
|
for (byte b : bytes) {
|
||||||
|
bh.consume(HexFormat.of().toHighHexDigit(b));
|
||||||
|
bh.consume(HexFormat.of().toLowHexDigit(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexUpper(Blackhole bh) {
|
||||||
|
for (byte b : bytes) {
|
||||||
|
bh.consume(HexFormat.of().withUpperCase().toHighHexDigit(b));
|
||||||
|
bh.consume(HexFormat.of().withUpperCase().toLowHexDigit(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexLowerCached(Blackhole bh) {
|
||||||
|
for (byte b : bytes) {
|
||||||
|
bh.consume(LOWER_FORMATTER.toHighHexDigit(b));
|
||||||
|
bh.consume(LOWER_FORMATTER.toLowHexDigit(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexUpperCached(Blackhole bh) {
|
||||||
|
for (byte b : bytes) {
|
||||||
|
bh.consume(UPPER_FORMATTER.toHighHexDigit(b));
|
||||||
|
bh.consume(UPPER_FORMATTER.toLowHexDigit(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexDigitsByte(Blackhole bh) {
|
||||||
|
for (byte b : bytes) {
|
||||||
|
bh.consume(LOWER_FORMATTER.toHexDigits(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexDigitsShort(Blackhole bh) {
|
||||||
|
for (short b : bytes) {
|
||||||
|
bh.consume(LOWER_FORMATTER.toHexDigits(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexDigitsInt(Blackhole bh) {
|
||||||
|
for (int b : bytes) {
|
||||||
|
bh.consume(LOWER_FORMATTER.toHexDigits(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void toHexDigitsLong(Blackhole bh) {
|
||||||
|
for (long b : bytes) {
|
||||||
|
bh.consume(LOWER_FORMATTER.toHexDigits(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user