Created
February 12, 2021 13:43
-
-
Save ctubbsii/380369779def7da20fa5bcfffa34418e to your computer and use it in GitHub Desktop.
Benchmark to test StringInterning performance
This file contains 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.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