Created
February 27, 2023 10:08
-
-
Save Palmr/9bec298ccb7501fc0064f183fd9fc575 to your computer and use it in GitHub Desktop.
Benchmarking performance of String Repeating in Java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example; | |
import org.openjdk.jmh.annotations.*; | |
import org.openjdk.jmh.infra.Blackhole; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.nio.charset.StandardCharsets; | |
import java.util.Arrays; | |
import java.util.concurrent.TimeUnit; | |
@BenchmarkMode(Mode.Throughput) | |
@OutputTimeUnit(TimeUnit.MILLISECONDS) | |
@Warmup(iterations = 5, time = 1) | |
@Measurement(iterations = 5, time = 1) | |
@Fork(2) | |
@Threads(1) | |
@State(Scope.Benchmark) | |
public class StringRepeatBenchmark { | |
private int REPEATS = 2048; | |
private static final String STRANG = "0123456789abcdef"; | |
private static final Field STRING_VALUE; | |
private static final Field STRING_CODER; | |
private static final Constructor<String> STRING_CONSTRUCTOR; | |
static { | |
try { | |
STRING_VALUE = String.class.getDeclaredField("value"); | |
STRING_VALUE.setAccessible(true); | |
STRING_CODER = String.class.getDeclaredField("coder"); | |
STRING_CODER.setAccessible(true); | |
STRING_CONSTRUCTOR = String.class.getDeclaredConstructor(byte[].class, byte.class); | |
STRING_CONSTRUCTOR.setAccessible(true); | |
} catch (NoSuchFieldException | NoSuchMethodException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
@Benchmark | |
public void stringBuilderAppend(final Blackhole bh) { | |
StringBuilder stringBuilder = new StringBuilder(STRANG.length() * REPEATS); | |
for (int i = 0; i < REPEATS; i++) { | |
stringBuilder.append(STRANG); | |
} | |
bh.consume(stringBuilder.toString()); | |
} | |
@Benchmark | |
public void stringBuilderAppendSam(final Blackhole bh) { | |
final var sam = REPEATS; | |
StringBuilder stringBuilder = new StringBuilder(STRANG.length() * sam); | |
for (int i = 0; i < sam; i++) { | |
stringBuilder.append(STRANG); | |
} | |
bh.consume(stringBuilder.toString()); | |
} | |
@Benchmark | |
public void stringRepeat(final Blackhole bh) { | |
bh.consume(STRANG.repeat(REPEATS)); | |
} | |
@Benchmark | |
public void logNRepeatsUnsafeAccess(final Blackhole bh) throws IllegalAccessException { | |
bh.consume(logNRepeatsUnsafeAccess(STRANG, REPEATS)); | |
} | |
@Benchmark | |
public void logNRepeatsUnsafeAccessUnsafeConstruct(final Blackhole bh) throws IllegalAccessException, InvocationTargetException, InstantiationException { | |
bh.consume(logNRepeatsUnsafeAccessUnsafeConstruct(STRANG, REPEATS)); | |
} | |
@Benchmark | |
public void logNRepeatsGetBytes(final Blackhole bh) { | |
bh.consume(logNRepeatsGetBytes(STRANG, REPEATS)); | |
} | |
@Benchmark | |
public void nRepeats(final Blackhole bh) { | |
bh.consume(nRepeats(STRANG, REPEATS)); | |
} | |
@Benchmark | |
public void stringRepeatPayingOurCostOfGettingBytes(final Blackhole bh) { | |
bh.consume(stringRepeatPayingOurCostOfGettingBytes(STRANG, REPEATS)); | |
} | |
private static String logNRepeatsGetBytes(final String input, final int count) { | |
byte[] inputBytes = input.getBytes(StandardCharsets.US_ASCII); | |
int buffySize = inputBytes.length * count; | |
byte[] buffy = new byte[buffySize]; | |
System.arraycopy(inputBytes, 0, buffy, 0, inputBytes.length); | |
int buffyEndPointer = inputBytes.length; | |
do { | |
System.arraycopy(buffy, 0, buffy, buffyEndPointer, buffyEndPointer); | |
buffyEndPointer <<= 1; | |
} while (buffyEndPointer != buffySize); | |
return new String(buffy, StandardCharsets.US_ASCII); | |
} | |
private static String logNRepeatsUnsafeAccess(final String input, final int count) throws IllegalAccessException { | |
byte[] inputBytes = (byte[])STRING_VALUE.get(input); | |
int buffySize = inputBytes.length * count; | |
byte[] buffy = new byte[buffySize]; | |
System.arraycopy(inputBytes, 0, buffy, 0, inputBytes.length); | |
int buffyEndPointer = inputBytes.length; | |
do { | |
System.arraycopy(buffy, 0, buffy, buffyEndPointer, buffyEndPointer); | |
buffyEndPointer <<= 1; | |
} while (buffyEndPointer != buffySize); | |
return new String(buffy, StandardCharsets.US_ASCII); | |
} | |
private static String logNRepeatsUnsafeAccessUnsafeConstruct(final String input, final int count) throws IllegalAccessException, InvocationTargetException, InstantiationException { | |
byte[] inputBytes = (byte[])STRING_VALUE.get(input); | |
int buffySize = inputBytes.length * count; | |
byte[] buffy = new byte[buffySize]; | |
System.arraycopy(inputBytes, 0, buffy, 0, inputBytes.length); | |
int buffyEndPointer = inputBytes.length; | |
do { | |
System.arraycopy(buffy, 0, buffy, buffyEndPointer, buffyEndPointer); | |
buffyEndPointer <<= 1; | |
} while (buffyEndPointer != buffySize); | |
return STRING_CONSTRUCTOR.newInstance(buffy, (byte)STRING_CODER.get(input)); | |
} | |
private static String nRepeats(final String input, final int count) | |
{ | |
byte[] inputBytes = input.getBytes(StandardCharsets.US_ASCII); | |
int buffySize = inputBytes.length * count; | |
byte[] buffy = new byte[buffySize]; | |
for (int i = 0; i < count; i++) { | |
System.arraycopy(inputBytes, 0, buffy, i * inputBytes.length, inputBytes.length); | |
} | |
return new String(buffy, StandardCharsets.US_ASCII); | |
} | |
public String stringRepeatPayingOurCostOfGettingBytes(final String input, final int count) { | |
if (count < 0) { | |
throw new IllegalArgumentException("count is negative: " + count); | |
} | |
if (count == 1) { | |
return input; | |
} | |
byte[] value = input.getBytes(StandardCharsets.US_ASCII); | |
final int len = value.length; | |
if (len == 0 || count == 0) { | |
return ""; | |
} | |
if (len == 1) { | |
final byte[] single = new byte[count]; | |
Arrays.fill(single, value[0]); | |
return new String(single, StandardCharsets.US_ASCII); | |
} | |
if (Integer.MAX_VALUE / count < len) { | |
throw new OutOfMemoryError("Repeating " + len + " bytes String " + count + | |
" times will produce a String exceeding maximum size."); | |
} | |
final int limit = len * count; | |
final byte[] multiple = new byte[limit]; | |
System.arraycopy(value, 0, multiple, 0, len); | |
int copied = len; | |
for (; copied < limit - copied; copied <<= 1) { | |
System.arraycopy(multiple, 0, multiple, copied, copied); | |
} | |
System.arraycopy(multiple, 0, multiple, copied, limit - copied); | |
return new String(multiple, StandardCharsets.US_ASCII); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment