Skip to content

Commit 3e82163

Browse files
committed
Update Jmh benchmark with JDK17 emulation
1 parent 509644d commit 3e82163

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed

src/test/java/at/favre/lib/bytes/EncodingHexJmhBenchmark.java

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424
import org.openjdk.jmh.annotations.*;
2525

26+
import java.io.IOException;
2627
import java.math.BigInteger;
2728
import java.nio.ByteOrder;
29+
import java.nio.charset.StandardCharsets;
2830
import java.util.HashMap;
2931
import java.util.Map;
32+
import java.util.Objects;
3033
import java.util.Random;
3134
import java.util.concurrent.TimeUnit;
3235

@@ -128,6 +131,7 @@ public class EncodingHexJmhBenchmark {
128131
private BinaryToTextEncoding.EncoderDecoder option3;
129132
private BinaryToTextEncoding.EncoderDecoder option4;
130133
private BinaryToTextEncoding.EncoderDecoder option5;
134+
private BinaryToTextEncoding.EncoderDecoder option6;
131135
private Random random;
132136

133137
@Setup(Level.Trial)
@@ -139,6 +143,7 @@ public void setup() {
139143
option3 = new BigIntegerHexEncoder();
140144
option4 = new OldBytesImplementation();
141145
option5 = new StackOverflowAnswer2Encoder();
146+
option6 = new Jdk17HexFormat();
142147

143148
rndMap = new HashMap<>();
144149
int[] lengths = new int[]{4, 8, 16, 32, 128, 512, 1000000};
@@ -176,6 +181,11 @@ public String encodeStackOverflowCode2() {
176181
return encodeDecode(option5);
177182
}
178183

184+
@Benchmark
185+
public String encodeHexFormatJdk17() {
186+
return encodeDecode(option6);
187+
}
188+
179189
private String encodeDecode(BinaryToTextEncoding.EncoderDecoder encoder) {
180190
Bytes[] bytes = rndMap.get(byteLength);
181191
int rndNum = random.nextInt(bytes.length);
@@ -285,4 +295,140 @@ public byte[] decode(CharSequence encoded) {
285295
throw new UnsupportedOperationException();
286296
}
287297
}
298+
299+
/**
300+
* Copy of the JDK 17 implementation of HexFormat, only difference is that we need to create new strings, while
301+
* the JDK can create strings without byte copy, I cant here.
302+
*/
303+
static final class Jdk17HexFormat implements BinaryToTextEncoding.EncoderDecoder {
304+
private static final byte[] LOWERCASE_DIGITS = {
305+
'0', '1', '2', '3', '4', '5', '6', '7',
306+
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
307+
};
308+
private final String delimiter = "";
309+
private final String prefix = "";
310+
private final String suffix = "";
311+
private final byte[] digits = LOWERCASE_DIGITS;
312+
313+
@Override
314+
public String encode(byte[] byteArray, ByteOrder byteOrder) {
315+
return formatHex(byteArray, 0, byteArray.length);
316+
}
317+
318+
private String formatHex(byte[] bytes, int fromIndex, int toIndex) {
319+
Objects.requireNonNull(bytes, "bytes");
320+
//Objects.checkFromToIndex(fromIndex, toIndex, bytes.length);
321+
if (toIndex - fromIndex == 0) {
322+
return "";
323+
}
324+
// Format efficiently if possible
325+
String s = formatOptDelimiter(bytes, fromIndex, toIndex);
326+
if (s == null) {
327+
long stride = prefix.length() + 2L + suffix.length() + delimiter.length();
328+
int capacity = checkMaxArraySize((toIndex - fromIndex) * stride - delimiter.length());
329+
StringBuilder sb = new StringBuilder(capacity);
330+
formatHex(sb, bytes, fromIndex, toIndex);
331+
s = sb.toString();
332+
}
333+
return s;
334+
}
335+
336+
private <A extends Appendable> A formatHex(A out, byte[] bytes, int fromIndex, int toIndex) {
337+
Objects.requireNonNull(out, "out");
338+
Objects.requireNonNull(bytes, "bytes");
339+
//Objects.checkFromToIndex(fromIndex, toIndex, bytes.length);
340+
341+
int length = toIndex - fromIndex;
342+
if (length > 0) {
343+
try {
344+
String between = suffix + delimiter + prefix;
345+
out.append(prefix);
346+
toHexDigits(out, bytes[fromIndex]);
347+
if (between.isEmpty()) {
348+
for (int i = 1; i < length; i++) {
349+
toHexDigits(out, bytes[fromIndex + i]);
350+
}
351+
} else {
352+
for (int i = 1; i < length; i++) {
353+
out.append(between);
354+
toHexDigits(out, bytes[fromIndex + i]);
355+
}
356+
}
357+
out.append(suffix);
358+
} catch (IOException ioe) {
359+
throw new RuntimeException(ioe.getMessage(), ioe);
360+
}
361+
}
362+
return out;
363+
}
364+
365+
private <A extends Appendable> A toHexDigits(A out, byte value) {
366+
Objects.requireNonNull(out, "out");
367+
try {
368+
out.append(toHighHexDigit(value));
369+
out.append(toLowHexDigit(value));
370+
return out;
371+
} catch (IOException ioe) {
372+
throw new RuntimeException(ioe.getMessage(), ioe);
373+
}
374+
}
375+
376+
private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) {
377+
byte[] rep;
378+
if (!prefix.isEmpty() || !suffix.isEmpty()) {
379+
return null;
380+
}
381+
int length = toIndex - fromIndex;
382+
if (delimiter.isEmpty()) {
383+
// Allocate the byte array and fill in the hex pairs for each byte
384+
rep = new byte[checkMaxArraySize(length * 2L)];
385+
for (int i = 0; i < length; i++) {
386+
rep[i * 2] = (byte) toHighHexDigit(bytes[fromIndex + i]);
387+
rep[i * 2 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]);
388+
}
389+
} else if (delimiter.length() == 1 && delimiter.charAt(0) < 256) {
390+
// Allocate the byte array and fill in the characters for the first byte
391+
// Then insert the delimiter and hexadecimal characters for each of the remaining bytes
392+
char sep = delimiter.charAt(0);
393+
rep = new byte[checkMaxArraySize(length * 3L - 1L)];
394+
rep[0] = (byte) toHighHexDigit(bytes[fromIndex]);
395+
rep[1] = (byte) toLowHexDigit(bytes[fromIndex]);
396+
for (int i = 1; i < length; i++) {
397+
rep[i * 3 - 1] = (byte) sep;
398+
rep[i * 3] = (byte) toHighHexDigit(bytes[fromIndex + i]);
399+
rep[i * 3 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]);
400+
}
401+
} else {
402+
// Delimiter formatting not to a single byte
403+
return null;
404+
}
405+
try {
406+
// Return a new string using the bytes without making a copy -> we cant use this here as we dont have access to JavaLangAccess
407+
//return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1);
408+
return new String(rep, StandardCharsets.ISO_8859_1);
409+
} catch (Exception cce) {
410+
throw new AssertionError(cce);
411+
}
412+
}
413+
414+
private char toHighHexDigit(int value) {
415+
return (char) digits[(value >> 4) & 0xf];
416+
}
417+
418+
private char toLowHexDigit(int value) {
419+
return (char) digits[value & 0xf];
420+
}
421+
422+
private static int checkMaxArraySize(long length) {
423+
if (length > Integer.MAX_VALUE)
424+
throw new OutOfMemoryError("String size " + length +
425+
" exceeds maximum " + Integer.MAX_VALUE);
426+
return (int) length;
427+
}
428+
429+
@Override
430+
public byte[] decode(CharSequence encoded) {
431+
throw new UnsupportedOperationException();
432+
}
433+
}
288434
}

0 commit comments

Comments
 (0)