Skip to content

Instantly share code, notes, and snippets.

@ctubbsii
Created February 12, 2021 13:43
Show Gist options
  • Save ctubbsii/380369779def7da20fa5bcfffa34418e to your computer and use it in GitHub Desktop.
Save ctubbsii/380369779def7da20fa5bcfffa34418e to your computer and use it in GitHub Desktop.
Benchmark to test StringInterning performance
package org.apache.accumulo.core.util;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Derived from https://gist.githubusercontent.com/npiguet/2715360/raw/18112f1e0c689ba9818beba779e4e26c92cdb555/InternBench.java
* Add concurrency testing, and test Accumulo's implementation.
*/
public class InternerBenchmark {
private static final String FMT_STRING = "| %17s | %9d | %16d | %17d |%n";
private static final String FMT_STRING_HDR = "|%s|%s|%s|%s|%n";
private static long timerStart = 0;
private static long timerEnd = 0;
private static int NUM;
private static ExecutorService exec;
public static void main(String[] args) {
NUM = 1_000_000;
exec = Executors.newFixedThreadPool(100);
startTimer();
Set<String> candidates = new HashSet<>();
Random rand = new Random();
for (int i = 0; i < NUM; i++) {
String candidate = Long.toString(rand.nextLong(), 36);
candidates.add(candidate);
}
System.out.printf("Generated candidate strings in %d ms%n", endTimer());
System.out.printf(FMT_STRING_HDR, " Implementation ", " load (ms) ", " lookup same (ms) ",
" lookup equal (ms) ");
System.out.printf(FMT_STRING_HDR, "-------------------", "-----------", "------------------",
"-------------------");
if (args.length > 0) {
if (args[0].equals("map")) {
putAll(candidates);
} else if (args[0].equals("weak")) {
putAllWeak(candidates);
} else if (args[0].equals("intern")) {
internAll(candidates);
} else if (args[0].equals("new")) {
putAllNew(candidates);
} else {
System.out.println("Bad arguments: " + Arrays.toString(args));
}
} else {
putAllWeak(candidates);
// putAllNew(candidates);
// putAll(candidates);
// internAll(candidates);
}
exec.shutdown();
}
private static long submit(Set<String> set, Function<String,Callable<String>> func) {
startTimer();
submitMultiThreaded(set, func);
// submitSingleThreaded(set, func);
return endTimer();
}
private static void submitSingleThreaded(Set<String> set,
Function<String,Callable<String>> func) {
for (String s : set) {
try {
func.apply(s).call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private static void submitMultiThreaded(Set<String> set, Function<String,Callable<String>> func) {
set.stream().map(func).map(exec::submit).collect(Collectors.toList()).forEach(f -> {
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
});
}
private static void internAll(Set<String> candidates) {
Function<String,Callable<String>> func = s -> s::intern;
long load = submit(candidates, func);
long lookupSame = submit(candidates, func);
Set<String> copies = candidates.stream().map(String::new).collect(Collectors.toSet());
long lookupEquals = submit(copies, func);
System.out.printf(FMT_STRING, "String.intern()", load, lookupSame, lookupEquals);
}
private static String dedupe(String item, WeakHashMap<String,WeakReference<String>> pool) {
synchronized (pool) {
WeakReference<String> lref = pool.get(item);
if (lref != null) {
String loc = lref.get();
if (loc != null) {
return loc;
}
}
pool.put(item, new WeakReference<>(item));
return item;
}
}
private static void putAllNew(Set<String> candidates) {
var internerWarm = new Interner<String>();
// warmup the methods of ConcurrentHashMap and the other java.util.concurrent stuff
// startTimer();
IntStream.range(0, NUM / 100).mapToObj(Integer::toString).forEach(s -> {
internerWarm.intern(s);
internerWarm.intern(s);
});
IntStream.range(0, NUM / 100).mapToObj(Integer::toString).forEach(s -> internerWarm.intern(s));
// endTimer("JIT warmup done in %d ms");
var interner = new Interner<String>();
Function<String,Callable<String>> func = s -> () -> interner.intern(s);
long load = submit(candidates, func);
long lookupSame = submit(candidates, func);
Set<String> copies = candidates.stream().map(String::new).collect(Collectors.toSet());
long lookupEquals = submit(copies, func);
System.out.printf(FMT_STRING, "Interner", load, lookupSame, lookupEquals);
}
private static void putAllWeak(Set<String> candidates) {
WeakHashMap<String,WeakReference<String>> poolWarn = new WeakHashMap<>();
// warmup the methods of ConcurrentHashMap and the other java.util.concurrent stuff
// startTimer();
IntStream.range(0, NUM / 100).mapToObj(Integer::toString).forEach(s -> {
dedupe(s, poolWarn);
dedupe(s, poolWarn);
});
IntStream.range(0, NUM / 100).mapToObj(Integer::toString).forEach(s -> dedupe(s, poolWarn));
// endTimer("JIT warmup done in %d ms");
WeakHashMap<String,WeakReference<String>> pool = new WeakHashMap<>();
Function<String,Callable<String>> func = s -> () -> dedupe(s, pool);
long load = submit(candidates, func);
long lookupSame = submit(candidates, func);
Set<String> copies = candidates.stream().map(String::new).collect(Collectors.toSet());
long lookupEquals = submit(copies, func);
System.out.printf(FMT_STRING, "WeakHashMap", load, lookupSame, lookupEquals);
}
private static void putAll(Set<String> candidates) {
ConcurrentHashMap<String,String> poolWarm = new ConcurrentHashMap<>();
// warmup the methods of ConcurrentHashMap and the other java.util.concurrent stuff
// startTimer();
IntStream.range(0, NUM / 100).mapToObj(Integer::toString).forEach(s -> {
poolWarm.putIfAbsent(s, s);
poolWarm.putIfAbsent(s, s);
});
IntStream.range(0, NUM / 100).mapToObj(Integer::toString)
.forEach(s -> poolWarm.putIfAbsent(s, s));
// endTimer("JIT warmup done in %d ms");
ConcurrentHashMap<String,String> pool = new ConcurrentHashMap<>();
Function<String,Callable<String>> func = s -> () -> pool.putIfAbsent(s, s);
long load = submit(candidates, func);
long lookupSame = submit(candidates, func);
Set<String> copies = candidates.stream().map(String::new).collect(Collectors.toSet());
long lookupEquals = submit(copies, func);
System.out.printf(FMT_STRING, "ConcurrentHashMap", load, lookupSame, lookupEquals);
}
private static void startTimer() {
timerStart = System.nanoTime();
}
private static long endTimer() {
timerEnd = System.nanoTime();
return TimeUnit.NANOSECONDS.toMillis(timerEnd - timerStart);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment