Skip to content

Instantly share code, notes, and snippets.

@Palmr
Created February 27, 2023 10:08
Show Gist options
  • Save Palmr/9bec298ccb7501fc0064f183fd9fc575 to your computer and use it in GitHub Desktop.
Save Palmr/9bec298ccb7501fc0064f183fd9fc575 to your computer and use it in GitHub Desktop.
Benchmarking performance of String Repeating in Java
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